mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Update dialogs
This commit is contained in:
@@ -7,6 +7,10 @@ import android.os.IBinder
|
|||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import com.futo.platformplayer.UIDialogs.ActionStyle
|
import com.futo.platformplayer.UIDialogs.ActionStyle
|
||||||
import com.futo.platformplayer.logging.Logger
|
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.StateApp
|
||||||
import com.futo.platformplayer.states.StateUpdate
|
import com.futo.platformplayer.states.StateUpdate
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -14,6 +18,7 @@ import java.io.File
|
|||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
class UpdateDownloadService : Service() {
|
class UpdateDownloadService : Service() {
|
||||||
|
|
||||||
@@ -85,13 +90,16 @@ class UpdateDownloadService : Service() {
|
|||||||
job.cancel()
|
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 now = SystemClock.elapsedRealtime()
|
||||||
val force = progress == 100 && !indeterminate
|
val force = progress == 100 && !indeterminate
|
||||||
|
|
||||||
if (force || now - lastProgressUpdateElapsedMs >= MIN_PROGRESS_UPDATE_INTERVAL_MS) {
|
if (force || now - lastProgressUpdateElapsedMs >= MIN_PROGRESS_UPDATE_INTERVAL_MS) {
|
||||||
lastProgressUpdateElapsedMs = now
|
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 apkFile = StateUpdate.getApkFile(this, version)
|
||||||
val partialFile = StateUpdate.getPartialApkFile(this, version)
|
val partialFile = StateUpdate.getPartialApkFile(this, version)
|
||||||
|
|
||||||
|
var announcement: SessionAnnouncement? = null;
|
||||||
try {
|
try {
|
||||||
if (apkFile.exists() && apkFile.length() > 0L) {
|
if (apkFile.exists() && apkFile.length() > 0L) {
|
||||||
Logger.i(TAG, "APK already downloaded at ${apkFile.absolutePath}")
|
Logger.i(TAG, "APK already downloaded at ${apkFile.absolutePath}")
|
||||||
@@ -106,6 +115,14 @@ class UpdateDownloadService : Service() {
|
|||||||
return
|
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
|
var backoffMs = INITIAL_BACKOFF_MS
|
||||||
|
|
||||||
for (attempt in 0 until MAX_RETRIES) {
|
for (attempt in 0 until MAX_RETRIES) {
|
||||||
@@ -115,7 +132,13 @@ class UpdateDownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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 (!cancelRequested) {
|
||||||
if (apkFile.exists()) {
|
if (apkFile.exists()) {
|
||||||
@@ -145,6 +168,12 @@ class UpdateDownloadService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
try {
|
||||||
|
if (announcement != null) {
|
||||||
|
StateAnnouncement.instance.closeAnnouncement(announcement.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(ex: Throwable){}
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
cancelRequested = false
|
cancelRequested = false
|
||||||
stopForeground(Service.STOP_FOREGROUND_REMOVE)
|
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
|
var startOffset = if (partialFile.exists()) partialFile.length() else 0L
|
||||||
Logger.i(TAG, "Starting download. url=$url, existingBytes=$startOffset")
|
Logger.i(TAG, "Starting download. url=$url, existingBytes=$startOffset")
|
||||||
|
|
||||||
@@ -204,7 +233,7 @@ class UpdateDownloadService : Service() {
|
|||||||
progress > 100 -> 100
|
progress > 100 -> 100
|
||||||
else -> progress
|
else -> progress
|
||||||
}
|
}
|
||||||
throttledUpdateDownloadProgress(version, safeProgress, indeterminate = false)
|
throttledUpdateDownloadProgress(version, safeProgress, indeterminate = false, onProgress)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throttledUpdateDownloadProgress(version, progress = 0, indeterminate = true)
|
throttledUpdateDownloadProgress(version, progress = 0, indeterminate = true)
|
||||||
@@ -250,6 +279,18 @@ class UpdateDownloadService : Service() {
|
|||||||
UpdateNotificationManager.cancelAll(ctx)
|
UpdateNotificationManager.cancelAll(ctx)
|
||||||
UpdateInstaller.startInstall(ctx, version, apkFile)
|
UpdateInstaller.startInstall(ctx, version, apkFile)
|
||||||
}, ActionStyle.PRIMARY, true));
|
}, 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) {
|
} catch (t: Throwable) {
|
||||||
Logger.w(TAG, "Failed to show in-app update downloaded dialog", t)
|
Logger.w(TAG, "Failed to show in-app update downloaded dialog", t)
|
||||||
updateDownloadedDialog = null
|
updateDownloadedDialog = null
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import com.futo.platformplayer.UIDialogs
|
|||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||||
import com.futo.platformplayer.constructs.Event0
|
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.dialogs.PluginUpdateDialog
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.ImageVariable
|
import com.futo.platformplayer.models.ImageVariable
|
||||||
@@ -118,8 +120,8 @@ class StateAnnouncement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Special Announcements
|
//Special Announcements
|
||||||
fun registerPluginUpdate(oldConfig: SourcePluginConfig, newConfig: SourcePluginConfig) {
|
fun registerPluginUpdate(oldConfig: SourcePluginConfig, newConfig: SourcePluginConfig): SessionAnnouncement {
|
||||||
registerAnnouncementSession(SessionAnnouncement(
|
val announcement = SessionAnnouncement(
|
||||||
"update-plugin-" + UUID.randomUUID().toString(),
|
"update-plugin-" + UUID.randomUUID().toString(),
|
||||||
"${newConfig.name} update v${newConfig.version} available!",
|
"${newConfig.name} update v${newConfig.version} available!",
|
||||||
"An update is available to upgrade from ${oldConfig.version} to ${newConfig.version}.",
|
"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, "updates", "Update", StateAnnouncement.ACTION_UPDATE_PLUGIN,
|
||||||
null, null,oldConfig.id,
|
null, null,oldConfig.id,
|
||||||
newConfig?.absoluteIconUrl?.let { ImageVariable.fromUrl(it) }
|
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) {
|
fun registerPluginUpdated(newConfig: SourcePluginConfig) {
|
||||||
registerAnnouncementSession(SessionAnnouncement(
|
registerAnnouncementSession(SessionAnnouncement(
|
||||||
@@ -141,17 +145,18 @@ class StateAnnouncement {
|
|||||||
).withExtraAction("Changelog", StateAnnouncement.ACTION_CHANGELOG, newConfig.id));
|
).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();
|
val id = "loading-" + UUID.randomUUID().toString();
|
||||||
registerAnnouncementSession(SessionAnnouncement(
|
val announcement = SessionAnnouncement(
|
||||||
id,
|
customId ?: id,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
AnnouncementType.ONGOING,
|
AnnouncementType.ONGOING,
|
||||||
null, "loading", null, null,
|
null, "loading", null, null,
|
||||||
null, null,null, icon
|
null, null,null, icon
|
||||||
));
|
);
|
||||||
return id;
|
registerAnnouncementSession(announcement);
|
||||||
|
return announcement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -338,9 +343,10 @@ class StateAnnouncement {
|
|||||||
return
|
return
|
||||||
|
|
||||||
closeAnnouncement(notifId);
|
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);
|
if(plugin.config.absoluteIconUrl != null) ImageVariable.fromUrl(plugin.config.absoluteIconUrl!!) else null);
|
||||||
|
|
||||||
|
val loadingId = loadingAnnouncement.id;
|
||||||
|
|
||||||
StateApp.instance.contextOrNull?.let { context ->
|
StateApp.instance.contextOrNull?.let { context ->
|
||||||
|
|
||||||
@@ -462,12 +468,26 @@ class SessionAnnouncement(
|
|||||||
var extraActionId: String? = null;
|
var extraActionId: String? = null;
|
||||||
var extraActionData: String? = null;
|
var extraActionData: String? = null;
|
||||||
|
|
||||||
|
var extraObj: Any? = null;
|
||||||
|
|
||||||
|
var progress: Double? = null;
|
||||||
|
val onProgressChanged = Event1<SessionAnnouncement>();
|
||||||
|
|
||||||
fun withExtraAction(name: String, id: String, data: String? = null): SessionAnnouncement {
|
fun withExtraAction(name: String, id: String, data: String? = null): SessionAnnouncement {
|
||||||
extraActionName = name;
|
extraActionName = name;
|
||||||
extraActionId = id;
|
extraActionId = id;
|
||||||
extraActionData = data;
|
extraActionData = data;
|
||||||
return this;
|
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) {
|
enum class AnnouncementType(val value : Int) {
|
||||||
|
|||||||
@@ -113,7 +113,10 @@ class StateUpdate {
|
|||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
dir.mkdirs();
|
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 {
|
fun getPartialApkFile(context: Context, version: Int): File {
|
||||||
@@ -121,7 +124,10 @@ class StateUpdate {
|
|||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
dir.mkdirs();
|
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() {
|
fun finish() {
|
||||||
|
|||||||
+26
@@ -7,8 +7,11 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.fragment.mainactivity.main.MainFragment
|
import com.futo.platformplayer.fragment.mainactivity.main.MainFragment
|
||||||
@@ -78,6 +81,7 @@ class NotificationOverlayView: ConstraintLayout {
|
|||||||
protected val _buttonExtra: LinearLayout
|
protected val _buttonExtra: LinearLayout
|
||||||
protected val _buttonExtraText: TextView
|
protected val _buttonExtraText: TextView
|
||||||
protected val _loader: LoaderView;
|
protected val _loader: LoaderView;
|
||||||
|
protected val _progress: ProgressBar;
|
||||||
|
|
||||||
init {
|
init {
|
||||||
_textName = _view.findViewById(R.id.text_name);
|
_textName = _view.findViewById(R.id.text_name);
|
||||||
@@ -90,6 +94,7 @@ class NotificationOverlayView: ConstraintLayout {
|
|||||||
_buttonExtraText = _view.findViewById(R.id.button_extra_text);
|
_buttonExtraText = _view.findViewById(R.id.button_extra_text);
|
||||||
_icon = _view.findViewById(R.id.icon);
|
_icon = _view.findViewById(R.id.icon);
|
||||||
_loader = _view.findViewById(R.id.loader);
|
_loader = _view.findViewById(R.id.loader);
|
||||||
|
_progress = _view.findViewById(R.id.progress);
|
||||||
|
|
||||||
_buttonIgnore.setOnClickListener {
|
_buttonIgnore.setOnClickListener {
|
||||||
_announcement.let {
|
_announcement.let {
|
||||||
@@ -116,8 +121,12 @@ class NotificationOverlayView: ConstraintLayout {
|
|||||||
|
|
||||||
|
|
||||||
override fun bind(value: Announcement) {
|
override fun bind(value: Announcement) {
|
||||||
|
val oldAnnouncement = _announcement;
|
||||||
_announcement = value;
|
_announcement = value;
|
||||||
|
|
||||||
|
if(oldAnnouncement is SessionAnnouncement)
|
||||||
|
oldAnnouncement.onProgressChanged.clear();
|
||||||
|
|
||||||
_textName.text = value.title;
|
_textName.text = value.title;
|
||||||
_textMetadata.text = value.msg;
|
_textMetadata.text = value.msg;
|
||||||
|
|
||||||
@@ -141,6 +150,23 @@ class NotificationOverlayView: ConstraintLayout {
|
|||||||
else {
|
else {
|
||||||
_buttonIgnore.visibility = View.VISIBLE;
|
_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 {
|
else {
|
||||||
_buttonExtra.visibility = View.GONE;
|
_buttonExtra.visibility = View.GONE;
|
||||||
|
|||||||
@@ -143,6 +143,17 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:layout_marginBottom="1dp"
|
||||||
|
android:progressTint="@color/primary"
|
||||||
|
/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/separator"
|
android:id="@+id/separator"
|
||||||
|
|||||||
Reference in New Issue
Block a user