From b84f3a3fa9278b4436eb6b97d397fa8377621c82 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Wed, 20 May 2026 01:21:19 +0530 Subject: [PATCH] updates: fix stale UI states & cancel-all coverage - AppUpdateItem now shows a disabled "Installing" button while the download is COMPLETED and awaiting install, instead of briefly reverting to "Update" between download-done and InstallerEvent.Installed. - Section header replaces all-or-nothing "Update all" toggle with an any-active check, so "Cancel all" appears as soon as a single update is in flight. Same treatment for the ownership-approval section. - Empty placeholder no longer hides the ignored-updates list when there are no live updates. - DownloadHelper.cancelAll also flips COMPLETED -> CANCELLED so the Cancel-all button gives immediate feedback for pending-install rows. - DownloadListItem: surface the failed icon for FAILED downloads too. --- .../compose/composable/DownloadListItem.kt | 5 ++- .../compose/composable/app/AppUpdateItem.kt | 11 ++++-- .../store/compose/ui/updates/UpdatesScreen.kt | 35 ++++++++++++++----- .../store/data/helper/DownloadHelper.kt | 9 +++-- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/aurora/store/compose/composable/DownloadListItem.kt b/app/src/main/java/com/aurora/store/compose/composable/DownloadListItem.kt index 46ea7012a..290e170bc 100644 --- a/app/src/main/java/com/aurora/store/compose/composable/DownloadListItem.kt +++ b/app/src/main/java/com/aurora/store/compose/composable/DownloadListItem.kt @@ -67,7 +67,10 @@ fun DownloadListItem(modifier: Modifier = Modifier, download: Download, onClick: tint = Color(0xFF4CAF50) ) } - } else if (download.status == DownloadStatus.CANCELLED) { + } else if ( + download.status == DownloadStatus.CANCELLED || + download.status == DownloadStatus.FAILED + ) { { Icon( painter = painterResource(R.drawable.ic_cancel), diff --git a/app/src/main/java/com/aurora/store/compose/composable/app/AppUpdateItem.kt b/app/src/main/java/com/aurora/store/compose/composable/app/AppUpdateItem.kt index 74cd57141..4316054fe 100644 --- a/app/src/main/java/com/aurora/store/compose/composable/app/AppUpdateItem.kt +++ b/app/src/main/java/com/aurora/store/compose/composable/app/AppUpdateItem.kt @@ -43,7 +43,8 @@ fun AppUpdateItem( onCancel: () -> Unit = {}, onUnignore: (() -> Unit)? = null ) { - val inProgress = download != null && download.status !in DownloadStatus.finished + val inProgress = download != null && !download.isFinished + val pendingInstall = download?.status == DownloadStatus.COMPLETED val progress = if (download?.status == DownloadStatus.DOWNLOADING) { download.progress.toFloat() } else { @@ -64,7 +65,7 @@ fun AppUpdateItem( AnimatedAppIcon( modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_medium)), iconUrl = update.iconURL, - inProgress = inProgress, + inProgress = inProgress || pendingInstall, progress = progress ) } @@ -99,6 +100,12 @@ fun AppUpdateItem( } } + pendingInstall -> { + OutlinedButton(onClick = {}, enabled = false) { + Text(stringResource(R.string.action_installing)) + } + } + inProgress -> { OutlinedButton(onClick = onCancel) { Text(stringResource(R.string.action_cancel)) diff --git a/app/src/main/java/com/aurora/store/compose/ui/updates/UpdatesScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/updates/UpdatesScreen.kt index b691aee53..50e4f67d8 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/updates/UpdatesScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/updates/UpdatesScreen.kt @@ -29,6 +29,7 @@ import com.aurora.store.compose.composable.SectionHeader import com.aurora.store.compose.composable.ShimmerUpdateItem import com.aurora.store.compose.composable.app.AppUpdateItem import com.aurora.store.compose.navigation.Destination +import com.aurora.store.data.model.DownloadStatus import com.aurora.store.data.room.download.Download import com.aurora.store.data.room.update.Update import com.aurora.store.viewmodel.all.UpdatesViewModel @@ -72,8 +73,8 @@ fun UpdatesScreen( } val (mainEntries, approvalEntries, incompatibleEntries) = groupedUpdates - val mainAllEnqueued = mainEntries.isNotEmpty() && - mainEntries.all { it.value?.isRunning == true } + val mainAnyActive = mainEntries.any { it.value.isActive() } + val approvalAnyActive = approvalEntries.any { it.value.isActive() } PullToRefreshBox( modifier = Modifier.fillMaxSize(), @@ -92,7 +93,7 @@ fun UpdatesScreen( } } - updateMap.isEmpty() -> { + updateMap.isEmpty() && ignoredUpdates.isEmpty() -> { Placeholder( modifier = Modifier.fillMaxSize(), painter = painterResource(R.drawable.ic_updates), @@ -119,8 +120,8 @@ fun UpdatesScreen( } ) val actionLabel = stringResource( - if (mainAllEnqueued) { - R.string.action_cancel + if (mainAnyActive) { + R.string.download_cancel_all } else { R.string.action_update_all } @@ -130,7 +131,7 @@ fun UpdatesScreen( trailing = { TextButton( onClick = { - if (mainAllEnqueued) { + if (mainAnyActive) { onCancelAll() } else { onRequestUpdateAll(mainEntries.map { it.key }) @@ -151,15 +152,28 @@ fun UpdatesScreen( if (approvalEntries.isNotEmpty()) { item(key = "header_approval") { + val actionLabel = stringResource( + if (approvalAnyActive) { + R.string.download_cancel_all + } else { + R.string.action_update_all + } + ) SectionHeader( title = stringResource(R.string.updates_approval_header), subtitle = stringResource(R.string.updates_approval_desc), trailing = { TextButton( onClick = { - onRequestUpdateAll(approvalEntries.map { it.key }) + if (approvalAnyActive) { + onCancelAll() + } else { + onRequestUpdateAll( + approvalEntries.map { it.key } + ) + } } - ) { Text(stringResource(R.string.action_update_all)) } + ) { Text(actionLabel) } } ) } @@ -212,6 +226,11 @@ fun UpdatesScreen( } } +private fun Download?.isActive(): Boolean { + if (this == null) return false + return !isFinished || status == DownloadStatus.COMPLETED +} + private fun LazyListScope.updateItems( entries: List>, keyPrefix: String, diff --git a/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt b/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt index d3c201c54..1fe336c11 100644 --- a/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt +++ b/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt @@ -165,9 +165,14 @@ class DownloadHelper @Inject constructor( * @param updatesOnly Whether to cancel only updates, defaults to false */ suspend fun cancelAll(updatesOnly: Boolean = false) { - // Cancel all enqueued downloads first to avoid triggering re-download + // Cancel queued/completed downloads first to avoid triggering re-download and to give + // the user immediate feedback for items that already finished downloading. The actual + // OS-level install for COMPLETED entries is wrapped in NonCancellable inside the + // worker and cannot be aborted — if it succeeds, the update row is removed via + // InstallerEvent.Installed; if it fails, the user can re-trigger an update. + val cancellableStatuses = setOf(DownloadStatus.QUEUED, DownloadStatus.COMPLETED) downloadDao.downloads().firstOrNull() - ?.filter { it.status == DownloadStatus.QUEUED } + ?.filter { it.status in cancellableStatuses } ?.filter { if (updatesOnly) it.isInstalled else true } ?.forEach { downloadDao.updateStatus(it.packageName, DownloadStatus.CANCELLED)