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.
This commit is contained in:
@@ -67,7 +67,10 @@ fun DownloadListItem(modifier: Modifier = Modifier, download: Download, onClick:
|
|||||||
tint = Color(0xFF4CAF50)
|
tint = Color(0xFF4CAF50)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (download.status == DownloadStatus.CANCELLED) {
|
} else if (
|
||||||
|
download.status == DownloadStatus.CANCELLED ||
|
||||||
|
download.status == DownloadStatus.FAILED
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_cancel),
|
painter = painterResource(R.drawable.ic_cancel),
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ fun AppUpdateItem(
|
|||||||
onCancel: () -> Unit = {},
|
onCancel: () -> Unit = {},
|
||||||
onUnignore: (() -> Unit)? = null
|
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) {
|
val progress = if (download?.status == DownloadStatus.DOWNLOADING) {
|
||||||
download.progress.toFloat()
|
download.progress.toFloat()
|
||||||
} else {
|
} else {
|
||||||
@@ -64,7 +65,7 @@ fun AppUpdateItem(
|
|||||||
AnimatedAppIcon(
|
AnimatedAppIcon(
|
||||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_medium)),
|
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_medium)),
|
||||||
iconUrl = update.iconURL,
|
iconUrl = update.iconURL,
|
||||||
inProgress = inProgress,
|
inProgress = inProgress || pendingInstall,
|
||||||
progress = progress
|
progress = progress
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -99,6 +100,12 @@ fun AppUpdateItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingInstall -> {
|
||||||
|
OutlinedButton(onClick = {}, enabled = false) {
|
||||||
|
Text(stringResource(R.string.action_installing))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inProgress -> {
|
inProgress -> {
|
||||||
OutlinedButton(onClick = onCancel) {
|
OutlinedButton(onClick = onCancel) {
|
||||||
Text(stringResource(R.string.action_cancel))
|
Text(stringResource(R.string.action_cancel))
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.aurora.store.compose.composable.SectionHeader
|
|||||||
import com.aurora.store.compose.composable.ShimmerUpdateItem
|
import com.aurora.store.compose.composable.ShimmerUpdateItem
|
||||||
import com.aurora.store.compose.composable.app.AppUpdateItem
|
import com.aurora.store.compose.composable.app.AppUpdateItem
|
||||||
import com.aurora.store.compose.navigation.Destination
|
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.download.Download
|
||||||
import com.aurora.store.data.room.update.Update
|
import com.aurora.store.data.room.update.Update
|
||||||
import com.aurora.store.viewmodel.all.UpdatesViewModel
|
import com.aurora.store.viewmodel.all.UpdatesViewModel
|
||||||
@@ -72,8 +73,8 @@ fun UpdatesScreen(
|
|||||||
}
|
}
|
||||||
val (mainEntries, approvalEntries, incompatibleEntries) = groupedUpdates
|
val (mainEntries, approvalEntries, incompatibleEntries) = groupedUpdates
|
||||||
|
|
||||||
val mainAllEnqueued = mainEntries.isNotEmpty() &&
|
val mainAnyActive = mainEntries.any { it.value.isActive() }
|
||||||
mainEntries.all { it.value?.isRunning == true }
|
val approvalAnyActive = approvalEntries.any { it.value.isActive() }
|
||||||
|
|
||||||
PullToRefreshBox(
|
PullToRefreshBox(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
@@ -92,7 +93,7 @@ fun UpdatesScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMap.isEmpty() -> {
|
updateMap.isEmpty() && ignoredUpdates.isEmpty() -> {
|
||||||
Placeholder(
|
Placeholder(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
painter = painterResource(R.drawable.ic_updates),
|
painter = painterResource(R.drawable.ic_updates),
|
||||||
@@ -119,8 +120,8 @@ fun UpdatesScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
val actionLabel = stringResource(
|
val actionLabel = stringResource(
|
||||||
if (mainAllEnqueued) {
|
if (mainAnyActive) {
|
||||||
R.string.action_cancel
|
R.string.download_cancel_all
|
||||||
} else {
|
} else {
|
||||||
R.string.action_update_all
|
R.string.action_update_all
|
||||||
}
|
}
|
||||||
@@ -130,7 +131,7 @@ fun UpdatesScreen(
|
|||||||
trailing = {
|
trailing = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (mainAllEnqueued) {
|
if (mainAnyActive) {
|
||||||
onCancelAll()
|
onCancelAll()
|
||||||
} else {
|
} else {
|
||||||
onRequestUpdateAll(mainEntries.map { it.key })
|
onRequestUpdateAll(mainEntries.map { it.key })
|
||||||
@@ -151,15 +152,28 @@ fun UpdatesScreen(
|
|||||||
|
|
||||||
if (approvalEntries.isNotEmpty()) {
|
if (approvalEntries.isNotEmpty()) {
|
||||||
item(key = "header_approval") {
|
item(key = "header_approval") {
|
||||||
|
val actionLabel = stringResource(
|
||||||
|
if (approvalAnyActive) {
|
||||||
|
R.string.download_cancel_all
|
||||||
|
} else {
|
||||||
|
R.string.action_update_all
|
||||||
|
}
|
||||||
|
)
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
title = stringResource(R.string.updates_approval_header),
|
title = stringResource(R.string.updates_approval_header),
|
||||||
subtitle = stringResource(R.string.updates_approval_desc),
|
subtitle = stringResource(R.string.updates_approval_desc),
|
||||||
trailing = {
|
trailing = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
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(
|
private fun LazyListScope.updateItems(
|
||||||
entries: List<Map.Entry<Update, Download?>>,
|
entries: List<Map.Entry<Update, Download?>>,
|
||||||
keyPrefix: String,
|
keyPrefix: String,
|
||||||
|
|||||||
@@ -165,9 +165,14 @@ class DownloadHelper @Inject constructor(
|
|||||||
* @param updatesOnly Whether to cancel only updates, defaults to false
|
* @param updatesOnly Whether to cancel only updates, defaults to false
|
||||||
*/
|
*/
|
||||||
suspend fun cancelAll(updatesOnly: Boolean = 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()
|
downloadDao.downloads().firstOrNull()
|
||||||
?.filter { it.status == DownloadStatus.QUEUED }
|
?.filter { it.status in cancellableStatuses }
|
||||||
?.filter { if (updatesOnly) it.isInstalled else true }
|
?.filter { if (updatesOnly) it.isInstalled else true }
|
||||||
?.forEach {
|
?.forEach {
|
||||||
downloadDao.updateStatus(it.packageName, DownloadStatus.CANCELLED)
|
downloadDao.updateStatus(it.packageName, DownloadStatus.CANCELLED)
|
||||||
|
|||||||
Reference in New Issue
Block a user