From 77c417117a45712676f15591e73ff3095ff8be14 Mon Sep 17 00:00:00 2001 From: renato97 Date: Tue, 25 Nov 2025 19:01:21 +0000 Subject: [PATCH] Update v9.4.3: Manual Events Refresh & Background Sync --- app/build.gradle | 4 +- .../com/streamplayer/EventRepository.java | 15 +++ .../java/com/streamplayer/MainActivity.java | 103 +++++++++++++++++- app/src/main/res/layout/activity_main.xml | 30 ++++- app/src/main/res/values/strings.xml | 2 + dashboard/data/devices.json | 6 +- update-manifest.json | 12 +- 7 files changed, 152 insertions(+), 20 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ad48e17..8a5fe9e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "com.streamplayer" minSdk 21 targetSdk 33 - versionCode 94200 - versionName "9.4.2" + versionCode 94300 + versionName "9.4.3" buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"' } diff --git a/app/src/main/java/com/streamplayer/EventRepository.java b/app/src/main/java/com/streamplayer/EventRepository.java index 72e9931..e4eb294 100644 --- a/app/src/main/java/com/streamplayer/EventRepository.java +++ b/app/src/main/java/com/streamplayer/EventRepository.java @@ -73,6 +73,21 @@ public class EventRepository { }).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 { URL url = new URL(EVENTS_URL); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); diff --git a/app/src/main/java/com/streamplayer/MainActivity.java b/app/src/main/java/com/streamplayer/MainActivity.java index 1ca433e..012dff0 100644 --- a/app/src/main/java/com/streamplayer/MainActivity.java +++ b/app/src/main/java/com/streamplayer/MainActivity.java @@ -7,11 +7,14 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; import android.view.View; +import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import android.text.TextUtils; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.GridLayoutManager; @@ -24,14 +27,18 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.TimeUnit; public class MainActivity extends AppCompatActivity { + private static final long EVENT_PREFETCH_INTERVAL_MS = TimeUnit.HOURS.toMillis(1); + private RecyclerView sectionList; private RecyclerView contentList; private ProgressBar loadingIndicator; private TextView messageView; private TextView contentTitle; + private Button eventsRefreshButton; private ChannelAdapter channelAdapter; private EventAdapter eventAdapter; @@ -46,6 +53,9 @@ public class MainActivity extends AppCompatActivity { private AlertDialog updateDialog; private AlertDialog blockedDialog; private DeviceRegistry deviceRegistry; + private Handler eventPrefetchHandler; + private Runnable eventPrefetchRunnable; + private boolean isEventsRefreshing; @Override protected void onCreate(Bundle savedInstanceState) { @@ -57,6 +67,8 @@ public class MainActivity extends AppCompatActivity { loadingIndicator = findViewById(R.id.loading_indicator); messageView = findViewById(R.id.message_view); contentTitle = findViewById(R.id.content_title); + eventsRefreshButton = findViewById(R.id.events_refresh_button); + eventsRefreshButton.setOnClickListener(v -> manualRefreshEvents()); channelAdapter = new ChannelAdapter( channel -> openPlayer(channel.getName(), channel.getPageUrl())); @@ -113,6 +125,8 @@ public class MainActivity extends AppCompatActivity { } } }); + + startEventPrefetchScheduler(); } @Override @@ -138,6 +152,12 @@ public class MainActivity extends AppCompatActivity { if (deviceRegistry != null) { deviceRegistry.release(); } + stopEventPrefetchScheduler(); + } + + @Override + public void onBackPressed() { + closeAppCompletely(); } private void selectSection(int index) { @@ -157,6 +177,7 @@ public class MainActivity extends AppCompatActivity { } private void showChannels(SectionEntry section) { + updateEventsRefreshVisibility(false); contentTitle.setText(section.title); contentList.setLayoutManager(channelLayoutManager); contentList.setAdapter(channelAdapter); @@ -172,6 +193,7 @@ public class MainActivity extends AppCompatActivity { } private void showEvents() { + updateEventsRefreshVisibility(true); contentTitle.setText(currentSection != null ? currentSection.title : getString(R.string.section_events)); contentList.setLayoutManager(eventLayoutManager); contentList.setAdapter(eventAdapter); @@ -183,6 +205,9 @@ public class MainActivity extends AppCompatActivity { } private void loadEvents(boolean forceRefresh) { + if (currentSection != null && currentSection.type == SectionEntry.Type.EVENTS) { + setEventsRefreshing(true); + } loadingIndicator.setVisibility(View.VISIBLE); messageView.setVisibility(View.GONE); eventRepository.loadEvents(this, forceRefresh, new EventRepository.Callback() { @@ -196,6 +221,7 @@ public class MainActivity extends AppCompatActivity { } else { loadingIndicator.setVisibility(View.GONE); } + setEventsRefreshing(false); }); } @@ -205,11 +231,48 @@ public class MainActivity extends AppCompatActivity { loadingIndicator.setVisibility(View.GONE); messageView.setVisibility(View.VISIBLE); 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() { loadingIndicator.setVisibility(View.GONE); if (cachedEvents.isEmpty()) { @@ -254,7 +317,7 @@ public class MainActivity extends AppCompatActivity { if (mandatory) { builder.setCancelable(false); builder.setNegativeButton(R.string.update_action_close_app, - (dialog, which) -> finish()); + (dialog, which) -> closeAppCompletely()); } else { builder.setNegativeButton(R.string.update_action_later, null); } @@ -338,7 +401,7 @@ public class MainActivity extends AppCompatActivity { .setView(dialogView) .setCancelable(false) .setPositiveButton(R.string.device_blocked_close, - (dialog, which) -> finish()); + (dialog, which) -> closeAppCompletely()); if (hasToken) { builder.setNeutralButton(R.string.device_blocked_copy_token, (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(); } + 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() { return getResources().getInteger(R.integer.channel_grid_span); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c03b9e5..ca02484 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -72,14 +72,32 @@ app:layout_constraintStart_toEndOf="@id/divider" app:layout_constraintTop_toTopOf="parent"> - + android:gravity="center_vertical" + android:orientation="horizontal"> + + + +