fix(update): bind live download state to dialog and show real-time progress
This commit is contained in:
1 parent
fb509c3cae
commit
850d01ca89
5 files changed
+167
-34
No files matched your search
@@ -600,6 +600,23 @@ fun ChatScreen(
|
||||
?: error("Application not available")
|
||||
AppUpdateViewModel(app)
|
||||
}
|
||||
val appUpdateState by appUpdateViewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
val updateLifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
|
||||
androidx.compose.runtime.DisposableEffect(updateLifecycleOwner) {
|
||||
val observer =
|
||||
androidx.lifecycle.LifecycleEventObserver { _, event ->
|
||||
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) {
|
||||
if (appUpdateState is AppUpdateState.NeedsUnknownSourcesPermission) {
|
||||
appUpdateViewModel.resumeInstallAfterPermission()
|
||||
}
|
||||
}
|
||||
}
|
||||
updateLifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
updateLifecycleOwner.lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
if (UpdateNoticeManager.enabled && !AppUpdateCache.dismissed) {
|
||||
val noticeTag =
|
||||
@@ -616,8 +633,14 @@ fun ChatScreen(
|
||||
|
||||
if (AppUpdateCache.isDialogVisible) {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
val dialogState =
|
||||
if (appUpdateState !is AppUpdateState.Idle) {
|
||||
appUpdateState
|
||||
} else {
|
||||
updateNotice
|
||||
}
|
||||
AppUpdateDialog(
|
||||
state = updateNotice,
|
||||
state = dialogState,
|
||||
onDismiss = { AppUpdateCache.hideDialog() },
|
||||
onStartUpdate = { appUpdateViewModel.startUpdate() },
|
||||
onCancelDownload = { appUpdateViewModel.cancelDownload() },
|
||||
|
||||
@@ -29,6 +29,10 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -49,14 +53,19 @@ fun AppUpdateDialog(
|
||||
onNeverAskAgain: () -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
) {
|
||||
val tag = state.releaseTag().orEmpty()
|
||||
val available = state as? AppUpdateState.UpdateAvailable
|
||||
var cachedAvailable by remember { mutableStateOf(available) }
|
||||
if (available != null) {
|
||||
cachedAvailable = available
|
||||
}
|
||||
val currentAvailable = available ?: cachedAvailable
|
||||
val tag = state.releaseTag() ?: currentAvailable?.latestTag.orEmpty()
|
||||
val sizeMb =
|
||||
available?.let {
|
||||
currentAvailable?.let {
|
||||
val mb = it.sizeBytes / (1024 * 1024.0)
|
||||
"%.1f MB".format(mb)
|
||||
}
|
||||
val releaseNotes = available?.releaseNotes?.trim().orEmpty()
|
||||
val releaseNotes = currentAvailable?.releaseNotes?.trim().orEmpty()
|
||||
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Surface(
|
||||
@@ -156,6 +165,7 @@ fun AppUpdateDialog(
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val progressPercent = (state.progress * 100).toInt()
|
||||
Text(
|
||||
@@ -167,16 +177,35 @@ fun AppUpdateDialog(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
if (currentAvailable != null && currentAvailable.sizeBytes > 0) {
|
||||
val downloadedMb = (state.progress * currentAvailable.sizeBytes) / (1024 * 1024.0)
|
||||
val totalMb = currentAvailable.sizeBytes / (1024 * 1024.0)
|
||||
Text(
|
||||
text = "%.1f / %.1f MB".format(downloadedMb, totalMb),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (state.progress > 0f) {
|
||||
LinearProgressIndicator(
|
||||
progress = { state.progress },
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
)
|
||||
} else {
|
||||
LinearProgressIndicator(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
LinearProgressIndicator(
|
||||
progress = { state.progress },
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
OutlinedButton(
|
||||
onClick = onCancelDownload,
|
||||
@@ -188,11 +217,21 @@ fun AppUpdateDialog(
|
||||
}
|
||||
|
||||
is AppUpdateState.Installing -> {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_about_update_installing, tag),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_about_update_installing, tag),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
LinearProgressIndicator(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is AppUpdateState.NeedsUnknownSourcesPermission -> {
|
||||
|
||||
@@ -43,10 +43,14 @@ class AppUpdateViewModel(
|
||||
val state: StateFlow<AppUpdateState> = _state.asStateFlow()
|
||||
|
||||
private var downloadJob: kotlinx.coroutines.Job? = null
|
||||
private var lastAvailable: AppUpdateState.UpdateAvailable? = null
|
||||
|
||||
init {
|
||||
val cached = AppUpdateCache.state.value
|
||||
if (cached is AppUpdateState.UpdateAvailable || cached is AppUpdateState.UpToDate) {
|
||||
if (cached is AppUpdateState.UpdateAvailable) {
|
||||
lastAvailable = cached
|
||||
_state.value = cached
|
||||
} else if (cached is AppUpdateState.UpToDate) {
|
||||
_state.value = cached
|
||||
} else {
|
||||
checkForUpdate()
|
||||
@@ -83,12 +87,13 @@ class AppUpdateViewModel(
|
||||
}
|
||||
_state.value =
|
||||
if (isNewerVersion(info.tagName, currentVersion)) {
|
||||
AppUpdateState.UpdateAvailable(
|
||||
latestTag = info.tagName,
|
||||
apkUrl = apk.browserDownloadUrl,
|
||||
sizeBytes = apk.size,
|
||||
releaseNotes = info.body,
|
||||
)
|
||||
AppUpdateState
|
||||
.UpdateAvailable(
|
||||
latestTag = info.tagName,
|
||||
apkUrl = apk.browserDownloadUrl,
|
||||
sizeBytes = apk.size,
|
||||
releaseNotes = info.body,
|
||||
).also { lastAvailable = it }
|
||||
} else {
|
||||
AppUpdateState.UpToDate(latestTag = info.tagName)
|
||||
}
|
||||
@@ -100,7 +105,12 @@ class AppUpdateViewModel(
|
||||
|
||||
/** Download the release APK and launch the system installer. */
|
||||
fun startUpdate() {
|
||||
val available = _state.value as? AppUpdateState.UpdateAvailable ?: return
|
||||
val available =
|
||||
(_state.value as? AppUpdateState.UpdateAvailable)
|
||||
?: lastAvailable
|
||||
?: (AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable)
|
||||
?: return
|
||||
lastAvailable = available
|
||||
if (!canRequestInstalls()) {
|
||||
_state.value = AppUpdateState.NeedsUnknownSourcesPermission
|
||||
return
|
||||
@@ -129,7 +139,9 @@ class AppUpdateViewModel(
|
||||
fun cancelDownload() {
|
||||
downloadJob?.cancel()
|
||||
downloadJob = null
|
||||
val available = AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable
|
||||
val available =
|
||||
lastAvailable
|
||||
?: (AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable)
|
||||
if (available != null) {
|
||||
_state.value = available
|
||||
} else {
|
||||
@@ -139,7 +151,10 @@ class AppUpdateViewModel(
|
||||
|
||||
/** Dismiss the current update tag so it stops nagging. */
|
||||
fun dismissCurrentUpdate() {
|
||||
val tag = _state.value.releaseTag()
|
||||
val tag =
|
||||
_state.value.releaseTag()
|
||||
?: lastAvailable?.latestTag
|
||||
?: AppUpdateCache.state.value.releaseTag()
|
||||
if (tag != null) {
|
||||
AuthManager.setDismissedUpdateTag(tag)
|
||||
}
|
||||
@@ -152,8 +167,9 @@ class AppUpdateViewModel(
|
||||
if (canRequestInstalls()) {
|
||||
val dest = File(getApplication<Application>().cacheDir, APK_FILE_NAME)
|
||||
val available =
|
||||
_state.value as? AppUpdateState.UpdateAvailable
|
||||
?: AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable
|
||||
(_state.value as? AppUpdateState.UpdateAvailable)
|
||||
?: lastAvailable
|
||||
?: (AppUpdateCache.state.value as? AppUpdateState.UpdateAvailable)
|
||||
if (dest.exists() && dest.length() > 0 && available != null) {
|
||||
_state.value = AppUpdateState.Installing(available.latestTag)
|
||||
launchInstaller(dest)
|
||||
|
||||
@@ -178,10 +178,16 @@ private fun UpdateRow(
|
||||
}
|
||||
|
||||
is AppUpdateState.Downloading -> {
|
||||
LinearProgressIndicator(
|
||||
progress = { state.progress },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
)
|
||||
if (state.progress > 0f) {
|
||||
LinearProgressIndicator(
|
||||
progress = { state.progress },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
)
|
||||
} else {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is AppUpdateState.NeedsUnknownSourcesPermission -> {
|
||||
|
||||
Reference in new issue
Block a user