1 Commits

Author SHA1 Message Date
77c417117a Update v9.4.3: Manual Events Refresh & Background Sync 2025-11-25 19:01:21 +00:00
7 changed files with 152 additions and 20 deletions

View File

@@ -8,8 +8,8 @@ android {
applicationId "com.streamplayer" applicationId "com.streamplayer"
minSdk 21 minSdk 21
targetSdk 33 targetSdk 33
versionCode 94200 versionCode 94300
versionName "9.4.2" versionName "9.4.3"
buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"' buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"'
} }

View File

@@ -73,6 +73,21 @@ public class EventRepository {
}).start(); }).start();
} }
public void prefetchEvents(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
new Thread(() -> {
try {
String json = downloadJson();
parseEvents(json);
prefs.edit()
.putString(KEY_JSON, json)
.putLong(KEY_TIMESTAMP, System.currentTimeMillis())
.apply();
} catch (IOException | JSONException ignored) {
}
}).start();
}
private String downloadJson() throws IOException { private String downloadJson() throws IOException {
URL url = new URL(EVENTS_URL); URL url = new URL(EVENTS_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();

View File

@@ -7,11 +7,14 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.text.TextUtils;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
@@ -24,14 +27,18 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private static final long EVENT_PREFETCH_INTERVAL_MS = TimeUnit.HOURS.toMillis(1);
private RecyclerView sectionList; private RecyclerView sectionList;
private RecyclerView contentList; private RecyclerView contentList;
private ProgressBar loadingIndicator; private ProgressBar loadingIndicator;
private TextView messageView; private TextView messageView;
private TextView contentTitle; private TextView contentTitle;
private Button eventsRefreshButton;
private ChannelAdapter channelAdapter; private ChannelAdapter channelAdapter;
private EventAdapter eventAdapter; private EventAdapter eventAdapter;
@@ -46,6 +53,9 @@ public class MainActivity extends AppCompatActivity {
private AlertDialog updateDialog; private AlertDialog updateDialog;
private AlertDialog blockedDialog; private AlertDialog blockedDialog;
private DeviceRegistry deviceRegistry; private DeviceRegistry deviceRegistry;
private Handler eventPrefetchHandler;
private Runnable eventPrefetchRunnable;
private boolean isEventsRefreshing;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -57,6 +67,8 @@ public class MainActivity extends AppCompatActivity {
loadingIndicator = findViewById(R.id.loading_indicator); loadingIndicator = findViewById(R.id.loading_indicator);
messageView = findViewById(R.id.message_view); messageView = findViewById(R.id.message_view);
contentTitle = findViewById(R.id.content_title); contentTitle = findViewById(R.id.content_title);
eventsRefreshButton = findViewById(R.id.events_refresh_button);
eventsRefreshButton.setOnClickListener(v -> manualRefreshEvents());
channelAdapter = new ChannelAdapter( channelAdapter = new ChannelAdapter(
channel -> openPlayer(channel.getName(), channel.getPageUrl())); channel -> openPlayer(channel.getName(), channel.getPageUrl()));
@@ -113,6 +125,8 @@ public class MainActivity extends AppCompatActivity {
} }
} }
}); });
startEventPrefetchScheduler();
} }
@Override @Override
@@ -138,6 +152,12 @@ public class MainActivity extends AppCompatActivity {
if (deviceRegistry != null) { if (deviceRegistry != null) {
deviceRegistry.release(); deviceRegistry.release();
} }
stopEventPrefetchScheduler();
}
@Override
public void onBackPressed() {
closeAppCompletely();
} }
private void selectSection(int index) { private void selectSection(int index) {
@@ -157,6 +177,7 @@ public class MainActivity extends AppCompatActivity {
} }
private void showChannels(SectionEntry section) { private void showChannels(SectionEntry section) {
updateEventsRefreshVisibility(false);
contentTitle.setText(section.title); contentTitle.setText(section.title);
contentList.setLayoutManager(channelLayoutManager); contentList.setLayoutManager(channelLayoutManager);
contentList.setAdapter(channelAdapter); contentList.setAdapter(channelAdapter);
@@ -172,6 +193,7 @@ public class MainActivity extends AppCompatActivity {
} }
private void showEvents() { private void showEvents() {
updateEventsRefreshVisibility(true);
contentTitle.setText(currentSection != null ? currentSection.title : getString(R.string.section_events)); contentTitle.setText(currentSection != null ? currentSection.title : getString(R.string.section_events));
contentList.setLayoutManager(eventLayoutManager); contentList.setLayoutManager(eventLayoutManager);
contentList.setAdapter(eventAdapter); contentList.setAdapter(eventAdapter);
@@ -183,6 +205,9 @@ public class MainActivity extends AppCompatActivity {
} }
private void loadEvents(boolean forceRefresh) { private void loadEvents(boolean forceRefresh) {
if (currentSection != null && currentSection.type == SectionEntry.Type.EVENTS) {
setEventsRefreshing(true);
}
loadingIndicator.setVisibility(View.VISIBLE); loadingIndicator.setVisibility(View.VISIBLE);
messageView.setVisibility(View.GONE); messageView.setVisibility(View.GONE);
eventRepository.loadEvents(this, forceRefresh, new EventRepository.Callback() { eventRepository.loadEvents(this, forceRefresh, new EventRepository.Callback() {
@@ -196,6 +221,7 @@ public class MainActivity extends AppCompatActivity {
} else { } else {
loadingIndicator.setVisibility(View.GONE); loadingIndicator.setVisibility(View.GONE);
} }
setEventsRefreshing(false);
}); });
} }
@@ -205,11 +231,48 @@ public class MainActivity extends AppCompatActivity {
loadingIndicator.setVisibility(View.GONE); loadingIndicator.setVisibility(View.GONE);
messageView.setVisibility(View.VISIBLE); messageView.setVisibility(View.VISIBLE);
messageView.setText(getString(R.string.message_events_error, message)); messageView.setText(getString(R.string.message_events_error, message));
setEventsRefreshing(false);
}); });
} }
}); });
} }
private void manualRefreshEvents() {
if (isEventsRefreshing) {
return;
}
if (currentSection == null || currentSection.type != SectionEntry.Type.EVENTS) {
return;
}
loadEvents(true);
}
private void updateEventsRefreshVisibility(boolean visible) {
if (eventsRefreshButton == null) {
return;
}
eventsRefreshButton.setVisibility(visible ? View.VISIBLE : View.GONE);
if (visible) {
eventsRefreshButton.setEnabled(!isEventsRefreshing);
eventsRefreshButton.setText(isEventsRefreshing
? getString(R.string.events_refreshing)
: getString(R.string.events_refresh_action));
}
}
private void setEventsRefreshing(boolean refreshing) {
isEventsRefreshing = refreshing;
if (eventsRefreshButton == null) {
return;
}
if (eventsRefreshButton.getVisibility() == View.VISIBLE) {
eventsRefreshButton.setEnabled(!refreshing);
eventsRefreshButton.setText(refreshing
? getString(R.string.events_refreshing)
: getString(R.string.events_refresh_action));
}
}
private void displayEvents() { private void displayEvents() {
loadingIndicator.setVisibility(View.GONE); loadingIndicator.setVisibility(View.GONE);
if (cachedEvents.isEmpty()) { if (cachedEvents.isEmpty()) {
@@ -254,7 +317,7 @@ public class MainActivity extends AppCompatActivity {
if (mandatory) { if (mandatory) {
builder.setCancelable(false); builder.setCancelable(false);
builder.setNegativeButton(R.string.update_action_close_app, builder.setNegativeButton(R.string.update_action_close_app,
(dialog, which) -> finish()); (dialog, which) -> closeAppCompletely());
} else { } else {
builder.setNegativeButton(R.string.update_action_later, null); builder.setNegativeButton(R.string.update_action_later, null);
} }
@@ -338,7 +401,7 @@ public class MainActivity extends AppCompatActivity {
.setView(dialogView) .setView(dialogView)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.device_blocked_close, .setPositiveButton(R.string.device_blocked_close,
(dialog, which) -> finish()); (dialog, which) -> closeAppCompletely());
if (hasToken) { if (hasToken) {
builder.setNeutralButton(R.string.device_blocked_copy_token, builder.setNeutralButton(R.string.device_blocked_copy_token,
(dialog, which) -> copyTokenToClipboard(tokenPart)); (dialog, which) -> copyTokenToClipboard(tokenPart));
@@ -358,6 +421,40 @@ public class MainActivity extends AppCompatActivity {
Toast.makeText(this, R.string.device_blocked_copy_success, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.device_blocked_copy_success, Toast.LENGTH_SHORT).show();
} }
private void startEventPrefetchScheduler() {
if (eventPrefetchHandler == null) {
eventPrefetchHandler = new Handler(Looper.getMainLooper());
}
if (eventPrefetchRunnable == null) {
eventPrefetchRunnable = new Runnable() {
@Override
public void run() {
if (eventRepository != null) {
eventRepository.prefetchEvents(getApplicationContext());
}
if (eventPrefetchHandler != null) {
eventPrefetchHandler.postDelayed(this, EVENT_PREFETCH_INTERVAL_MS);
}
}
};
} else {
eventPrefetchHandler.removeCallbacks(eventPrefetchRunnable);
}
eventPrefetchHandler.post(eventPrefetchRunnable);
}
private void stopEventPrefetchScheduler() {
if (eventPrefetchHandler != null && eventPrefetchRunnable != null) {
eventPrefetchHandler.removeCallbacks(eventPrefetchRunnable);
}
}
private void closeAppCompletely() {
finishAffinity();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
private int getSpanCount() { private int getSpanCount() {
return getResources().getInteger(R.integer.channel_grid_span); return getResources().getInteger(R.integer.channel_grid_span);
} }

View File

@@ -72,14 +72,32 @@
app:layout_constraintStart_toEndOf="@id/divider" app:layout_constraintStart_toEndOf="@id/divider"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<TextView <LinearLayout
android:id="@+id/content_title" android:id="@+id/content_header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/white" android:gravity="center_vertical"
android:textSize="18sp" android:orientation="horizontal">
android:textStyle="bold"
tools:text="Canales" /> <TextView
android:id="@+id/content_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Canales" />
<Button
android:id="@+id/events_refresh_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/events_refresh_action"
android:textAllCaps="false"
android:visibility="gone" />
</LinearLayout>
<ProgressBar <ProgressBar
android:id="@+id/loading_indicator" android:id="@+id/loading_indicator"

View File

@@ -46,4 +46,6 @@
<string name="device_blocked_copy_success">Código copiado al portapapeles</string> <string name="device_blocked_copy_success">Código copiado al portapapeles</string>
<string name="device_blocked_copy_error">No se pudo copiar el código</string> <string name="device_blocked_copy_error">No se pudo copiar el código</string>
<string name="device_registry_error">No se pudo registrar el dispositivo (%1$s)</string> <string name="device_registry_error">No se pudo registrar el dispositivo (%1$s)</string>
<string name="events_refresh_action">Actualizar ahora</string>
<string name="events_refreshing">Actualizando...</string>
</resources> </resources>

View File

@@ -6,8 +6,8 @@
"model": "SM-S928B", "model": "SM-S928B",
"manufacturer": "Samsung", "manufacturer": "Samsung",
"osVersion": "16 (API 36)", "osVersion": "16 (API 36)",
"appVersionName": "9.4.1", "appVersionName": "9.4.3",
"appVersionCode": 94100, "appVersionCode": 94300,
"firstSeen": "2025-11-23T22:31:13.359Z", "firstSeen": "2025-11-23T22:31:13.359Z",
"lastSeen": "2025-11-23T23:11:07.215Z", "lastSeen": "2025-11-23T23:11:07.215Z",
"blocked": false, "blocked": false,
@@ -23,4 +23,4 @@
"verifiedAt": "2025-11-23T22:33:11.942Z" "verifiedAt": "2025-11-23T22:33:11.942Z"
} }
} }
] ]

View File

@@ -1,10 +1,10 @@
{ {
"versionCode": 94100, "versionCode": 94300,
"versionName": "9.4.1", "versionName": "9.4.3",
"minSupportedVersionCode": 91000, "minSupportedVersionCode": 91000,
"forceUpdate": false, "forceUpdate": false,
"downloadUrl": "https://gitea.cbcren.online/renato97/app/releases/download/v9.4.1/StreamPlayer-v9.4.1.apk", "downloadUrl": "https://gitea.cbcren.online/renato97/app/releases/download/v9.4.3/StreamPlayer-v9.4.3.apk",
"fileName": "StreamPlayer-v9.4.1.apk", "fileName": "StreamPlayer-v9.4.3.apk",
"sizeBytes": 5944680, "sizeBytes": 5947283,
"notes": "StreamPlayer v9.4.1\n\nMejoras en esta versión:\n\n- Experiencia de reproducción optimizada e ininterrumpida\n- Mejores controles de administración y gestión de dispositivos\n- Funcionalidad de eliminación de registros con confirmación segura\n- Optimización de energía durante el uso de la aplicación\n- Interfaz administrativa mejorada con más opciones\n- Flujo de trabajo más eficiente para la gestión\n- Mejor respuesta y estabilidad general\n- Correcciones de usabilidad menores\n\nEsta actualización mejora tanto la experiencia de visualización como las herramientas de administración para un mejor control y uso de la aplicación." "notes": "StreamPlayer v9.4.3\n\nNovedades destacadas:\n\n- Botón \"Actualizar ahora\" en la sección de Eventos para refrescar manualmente la grilla.\n- Sincronización silenciosa cada 60 minutos para precargar nuevos eventos sin interrumpir la reproducción.\n- Cache persiste y se aplica automáticamente al reiniciar la app para asegurar datos frescos.\n- Cierre completo al salir para garantizar que cada inicio recargue con la última información disponible.\n- Correcciones y mejoras generales de estabilidad en la sección de eventos."
} }