From 8536861e09198f264805e6e583b5fc6c0ab28537 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Mon, 5 Jan 2026 23:56:53 +0100 Subject: [PATCH] Update dialogs --- .../platformplayer/UpdateDownloadService.kt | 51 +++++++++++++++++-- .../states/StateAnnouncement.kt | 38 ++++++++++---- .../futo/platformplayer/states/StateUpdate.kt | 10 +++- .../notification/NotificationOverlayView.kt | 26 ++++++++++ app/src/main/res/layout/list_announcement.xml | 11 ++++ 5 files changed, 120 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UpdateDownloadService.kt b/app/src/main/java/com/futo/platformplayer/UpdateDownloadService.kt index 3147ce62..b0609604 100644 --- a/app/src/main/java/com/futo/platformplayer/UpdateDownloadService.kt +++ b/app/src/main/java/com/futo/platformplayer/UpdateDownloadService.kt @@ -7,6 +7,10 @@ import android.os.IBinder import android.os.SystemClock import com.futo.platformplayer.UIDialogs.ActionStyle import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.ImageVariable +import com.futo.platformplayer.states.AnnouncementType +import com.futo.platformplayer.states.SessionAnnouncement +import com.futo.platformplayer.states.StateAnnouncement import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateUpdate import kotlinx.coroutines.* @@ -14,6 +18,7 @@ import java.io.File import java.io.FileOutputStream import java.net.HttpURLConnection import java.net.URL +import java.time.OffsetDateTime class UpdateDownloadService : Service() { @@ -85,13 +90,16 @@ class UpdateDownloadService : Service() { job.cancel() } - private fun throttledUpdateDownloadProgress(version: Int, progress: Int, indeterminate: Boolean) { + private fun throttledUpdateDownloadProgress(version: Int, progress: Int, indeterminate: Boolean, onProgress: ((Int) -> Unit)? = null) { val now = SystemClock.elapsedRealtime() val force = progress == 100 && !indeterminate if (force || now - lastProgressUpdateElapsedMs >= MIN_PROGRESS_UPDATE_INTERVAL_MS) { lastProgressUpdateElapsedMs = now - UpdateNotificationManager.updateDownloadProgress(this, version, progress, indeterminate) + UpdateNotificationManager.updateDownloadProgress(this, version, progress, indeterminate); + + if(onProgress != null) + onProgress.invoke(progress); } } @@ -99,6 +107,7 @@ class UpdateDownloadService : Service() { val apkFile = StateUpdate.getApkFile(this, version) val partialFile = StateUpdate.getPartialApkFile(this, version) + var announcement: SessionAnnouncement? = null; try { if (apkFile.exists() && apkFile.length() > 0L) { Logger.i(TAG, "APK already downloaded at ${apkFile.absolutePath}") @@ -106,6 +115,14 @@ class UpdateDownloadService : Service() { return } + try { + announcement = StateAnnouncement.instance.registerLoading("Downloading new version [${version}]", "New version is being downloaded..", + ImageVariable.fromResource(R.drawable.foreground)); + } + catch(ex: Exception){ + Logger.e(TAG, "Failed to set progress announcement", ex); + } + var backoffMs = INITIAL_BACKOFF_MS for (attempt in 0 until MAX_RETRIES) { @@ -115,7 +132,13 @@ class UpdateDownloadService : Service() { } try { - performDownload(StateUpdate.APK_URL, partialFile, version) + performDownload(StateUpdate.APK_URL, partialFile, version, { + try { + if (announcement != null) + announcement?.setProgress(it); + } + catch(ex: Throwable) {} + }) if (!cancelRequested) { if (apkFile.exists()) { @@ -145,6 +168,12 @@ class UpdateDownloadService : Service() { } } } finally { + try { + if (announcement != null) { + StateAnnouncement.instance.closeAnnouncement(announcement.id); + } + } + catch(ex: Throwable){} isDownloading = false cancelRequested = false stopForeground(Service.STOP_FOREGROUND_REMOVE) @@ -152,7 +181,7 @@ class UpdateDownloadService : Service() { } } - private fun performDownload(url: String, partialFile: File, version: Int) { + private fun performDownload(url: String, partialFile: File, version: Int, onProgress: ((Int)->Unit)? = null) { var startOffset = if (partialFile.exists()) partialFile.length() else 0L Logger.i(TAG, "Starting download. url=$url, existingBytes=$startOffset") @@ -204,7 +233,7 @@ class UpdateDownloadService : Service() { progress > 100 -> 100 else -> progress } - throttledUpdateDownloadProgress(version, safeProgress, indeterminate = false) + throttledUpdateDownloadProgress(version, safeProgress, indeterminate = false, onProgress) } } else { throttledUpdateDownloadProgress(version, progress = 0, indeterminate = true) @@ -250,6 +279,18 @@ class UpdateDownloadService : Service() { UpdateNotificationManager.cancelAll(ctx) UpdateInstaller.startInstall(ctx, version, apkFile) }, ActionStyle.PRIMARY, true)); + + try { + StateAnnouncement.instance.registerAnnouncement("install-update-apk", "Grayjay v${version} is ready!", "You can now install the new Grayjay version.", + AnnouncementType.SESSION, + OffsetDateTime.now(), "update", "Install", { + UpdateNotificationManager.cancelAll(ctx) + UpdateInstaller.startInstall(ctx, version, apkFile) + }); + } + catch(ex: Throwable) { + + } } catch (t: Throwable) { Logger.w(TAG, "Failed to show in-app update downloaded dialog", t) updateDownloadedDialog = null diff --git a/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt b/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt index c0ae1adf..3712b565 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt @@ -7,6 +7,8 @@ import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.dialogs.PluginUpdateDialog import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.ImageVariable @@ -118,8 +120,8 @@ class StateAnnouncement { } //Special Announcements - fun registerPluginUpdate(oldConfig: SourcePluginConfig, newConfig: SourcePluginConfig) { - registerAnnouncementSession(SessionAnnouncement( + fun registerPluginUpdate(oldConfig: SourcePluginConfig, newConfig: SourcePluginConfig): SessionAnnouncement { + val announcement = SessionAnnouncement( "update-plugin-" + UUID.randomUUID().toString(), "${newConfig.name} update v${newConfig.version} available!", "An update is available to upgrade from ${oldConfig.version} to ${newConfig.version}.", @@ -127,7 +129,9 @@ class StateAnnouncement { null, "updates", "Update", StateAnnouncement.ACTION_UPDATE_PLUGIN, null, null,oldConfig.id, newConfig?.absoluteIconUrl?.let { ImageVariable.fromUrl(it) } - ).withExtraAction("Changelog", StateAnnouncement.ACTION_CHANGELOG, oldConfig.id)); + ).withExtraAction("Changelog", StateAnnouncement.ACTION_CHANGELOG, oldConfig.id); + registerAnnouncementSession(announcement); + return announcement; } fun registerPluginUpdated(newConfig: SourcePluginConfig) { registerAnnouncementSession(SessionAnnouncement( @@ -141,17 +145,18 @@ class StateAnnouncement { ).withExtraAction("Changelog", StateAnnouncement.ACTION_CHANGELOG, newConfig.id)); } - fun registerLoading(title: String, description: String, icon: ImageVariable? = null): String { + fun registerLoading(title: String, description: String, icon: ImageVariable? = null, customId: String? = null): SessionAnnouncement { val id = "loading-" + UUID.randomUUID().toString(); - registerAnnouncementSession(SessionAnnouncement( - id, + val announcement = SessionAnnouncement( + customId ?: id, title, description, AnnouncementType.ONGOING, null, "loading", null, null, null, null,null, icon - )); - return id; + ); + registerAnnouncementSession(announcement); + return announcement; } @@ -338,9 +343,10 @@ class StateAnnouncement { return closeAnnouncement(notifId); - val loadingId = registerLoading("Updating ${plugin.config.name}..", "An update is in progress for ${plugin.config.name}.", + val loadingAnnouncement = registerLoading("Updating ${plugin.config.name}..", "An update is in progress for ${plugin.config.name}.", if(plugin.config.absoluteIconUrl != null) ImageVariable.fromUrl(plugin.config.absoluteIconUrl!!) else null); + val loadingId = loadingAnnouncement.id; StateApp.instance.contextOrNull?.let { context -> @@ -462,12 +468,26 @@ class SessionAnnouncement( var extraActionId: String? = null; var extraActionData: String? = null; + var extraObj: Any? = null; + + var progress: Double? = null; + val onProgressChanged = Event1(); + fun withExtraAction(name: String, id: String, data: String? = null): SessionAnnouncement { extraActionName = name; extraActionId = id; extraActionData = data; return this; } + + fun setProgress(progress: Double) { + this.progress = progress; + onProgressChanged?.emit(this); + } + fun setProgress(progress: Int) { + this.progress = progress.toDouble().div(100); + onProgressChanged?.emit(this); + } } enum class AnnouncementType(val value : Int) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateUpdate.kt b/app/src/main/java/com/futo/platformplayer/states/StateUpdate.kt index 2a39bb1b..1a000045 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateUpdate.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateUpdate.kt @@ -113,7 +113,10 @@ class StateUpdate { if (!dir.exists()) { dir.mkdirs(); } - return File(dir, "app-${DESIRED_ABI}-${version}.apk"); + val result = File(dir, "app-${DESIRED_ABI}-${version}.apk"); + //if(result.exists()) + // result.delete(); + return result; } fun getPartialApkFile(context: Context, version: Int): File { @@ -121,7 +124,10 @@ class StateUpdate { if (!dir.exists()) { dir.mkdirs(); } - return File(dir, "app-${DESIRED_ABI}-${version}.apk.part"); + val result = File(dir, "app-${DESIRED_ABI}-${version}.apk.part"); + //if(result.exists()) + // result.delete(); + return result; } fun finish() { diff --git a/app/src/main/java/com/futo/platformplayer/views/notification/NotificationOverlayView.kt b/app/src/main/java/com/futo/platformplayer/views/notification/NotificationOverlayView.kt index f18440da..84ce2375 100644 --- a/app/src/main/java/com/futo/platformplayer/views/notification/NotificationOverlayView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/notification/NotificationOverlayView.kt @@ -7,8 +7,11 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout +import android.widget.ProgressBar import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.recyclerview.widget.RecyclerView import com.futo.platformplayer.R import com.futo.platformplayer.fragment.mainactivity.main.MainFragment @@ -78,6 +81,7 @@ class NotificationOverlayView: ConstraintLayout { protected val _buttonExtra: LinearLayout protected val _buttonExtraText: TextView protected val _loader: LoaderView; + protected val _progress: ProgressBar; init { _textName = _view.findViewById(R.id.text_name); @@ -90,6 +94,7 @@ class NotificationOverlayView: ConstraintLayout { _buttonExtraText = _view.findViewById(R.id.button_extra_text); _icon = _view.findViewById(R.id.icon); _loader = _view.findViewById(R.id.loader); + _progress = _view.findViewById(R.id.progress); _buttonIgnore.setOnClickListener { _announcement.let { @@ -116,8 +121,12 @@ class NotificationOverlayView: ConstraintLayout { override fun bind(value: Announcement) { + val oldAnnouncement = _announcement; _announcement = value; + if(oldAnnouncement is SessionAnnouncement) + oldAnnouncement.onProgressChanged.clear(); + _textName.text = value.title; _textMetadata.text = value.msg; @@ -141,6 +150,23 @@ class NotificationOverlayView: ConstraintLayout { else { _buttonIgnore.visibility = View.VISIBLE; } + if(value.progress != null && value.announceType == AnnouncementType.ONGOING) { + _progress.isVisible = true; + _progress.min = 0; + _progress.max = 100; + value.onProgressChanged.subscribe { + val prog = it.progress; + if(prog == 0.toDouble() || prog == 100.toDouble()) { + _progress.isIndeterminate = true; + } + else { + _progress.isIndeterminate = false; + _progress.setProgress(it.progress?.times(100)?.toInt() ?: 0, false); + } + } + } + else + _progress.isVisible = false; } else { _buttonExtra.visibility = View.GONE; diff --git a/app/src/main/res/layout/list_announcement.xml b/app/src/main/res/layout/list_announcement.xml index 61576c59..e732931a 100644 --- a/app/src/main/res/layout/list_announcement.xml +++ b/app/src/main/res/layout/list_announcement.xml @@ -143,6 +143,17 @@ +