feat: cancel button on active downloads

- ActiveDownload now carries workerId (UUID string) for cancellation
- cancelDownload() calls WorkManager.cancelWorkById()
- Downloads tab shows ✕ button on in-progress download cards
This commit is contained in:
Mathieu Moran
2026-08-12 03:35:03 -04:00
parent 78cd05c4cb
commit b523d77e16
3 changed files with 46 additions and 13 deletions
@@ -23,6 +23,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
import java.util.UUID
data class DownloadProgress( data class DownloadProgress(
val path: String, val path: String,
@@ -37,8 +38,9 @@ data class DownloadedFile(
) )
data class ActiveDownload( data class ActiveDownload(
val workerId: String, // UUID string to cancel
val filename: String, val filename: String,
val progress: Int, // 0-100 val progress: Int,
val done: Boolean, val done: Boolean,
) )
@@ -140,19 +142,19 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application)
.build() .build()
val wm = WorkManager.getInstance(getApplication()) val wm = WorkManager.getInstance(getApplication())
val uuid = request.id
val workerId = uuid.toString()
wm.enqueue(request) wm.enqueue(request)
// Observe progress _activeDownloads.value = _activeDownloads.value + (filename to ActiveDownload(workerId, filename, 0, false))
val workerId = request.id
_activeDownloads.value = _activeDownloads.value + (filename to ActiveDownload(filename, 0, false))
wm.getWorkInfoByIdLiveData(workerId).observeForever { info -> wm.getWorkInfoByIdLiveData(uuid).observeForever { info ->
if (info == null) return@observeForever if (info == null) return@observeForever
val progress = info.progress.getInt("pct", 0) val progress = info.progress.getInt("pct", 0)
val done = info.state == WorkInfo.State.SUCCEEDED || info.state == WorkInfo.State.FAILED val done = info.state == WorkInfo.State.SUCCEEDED || info.state == WorkInfo.State.FAILED
_activeDownloads.value = _activeDownloads.value + (filename to ActiveDownload(filename, progress, done)) _activeDownloads.value = _activeDownloads.value + (filename to ActiveDownload(workerId, filename, progress, done))
when (info.state) { when (info.state) {
WorkInfo.State.SUCCEEDED -> { WorkInfo.State.SUCCEEDED -> {
@@ -169,7 +171,7 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application)
} }
_activeDownloads.value = _activeDownloads.value - filename _activeDownloads.value = _activeDownloads.value - filename
} }
else -> {} // running / enqueued else -> {}
} }
} }
@@ -197,6 +199,17 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application)
} }
} }
fun cancelDownload(dl: ActiveDownload) {
try {
val uuid = java.util.UUID.fromString(dl.workerId)
WorkManager.getInstance(getApplication()).cancelWorkById(uuid)
_activeDownloads.value = _activeDownloads.value - dl.filename
viewModelScope.launch {
_downloadEvents.emit("✕ Cancelled: ${dl.filename}")
}
} catch (_: Exception) {}
}
fun deleteVideo(video: VideoFile) { fun deleteVideo(video: VideoFile) {
viewModelScope.launch { viewModelScope.launch {
try { try {
@@ -37,6 +37,7 @@ fun DownloadsScreen(
files: List<DownloadedFile>, files: List<DownloadedFile>,
activeDownloads: Map<String, ActiveDownload>, activeDownloads: Map<String, ActiveDownload>,
onDelete: (DownloadedFile) -> Unit, onDelete: (DownloadedFile) -> Unit,
onCancel: (ActiveDownload) -> Unit,
onRefresh: () -> Unit, onRefresh: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
@@ -80,11 +81,29 @@ fun DownloadsScreen(
) )
) { ) {
Column(modifier = Modifier.padding(12.dp)) { Column(modifier = Modifier.padding(12.dp)) {
Text( Row(
text = if (dl.done) "✓ $filename" else "⏳ $filename", modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.bodyMedium, horizontalArrangement = Arrangement.SpaceBetween,
color = MaterialTheme.colorScheme.onSurface verticalAlignment = Alignment.CenterVertically
) ) {
Text(
text = if (dl.done) "✓ $filename" else "⏳ $filename",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.weight(1f)
)
if (!dl.done) {
Button(
onClick = { onCancel(dl) },
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
),
modifier = Modifier.height(32.dp)
) {
Text("✕", style = MaterialTheme.typography.bodySmall)
}
}
}
if (!dl.done) { if (!dl.done) {
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
LinearProgressIndicator( LinearProgressIndicator(
@@ -105,9 +105,10 @@ fun YarisMainScreen(client: OkHttpClient) {
onDelete = { dashcamVm.deleteVideo(it) }, onDelete = { dashcamVm.deleteVideo(it) },
) )
1 -> DownloadsScreen( 1 -> DownloadsScreen(
activeDownloads = activeDownloads,
files = downloadedFiles, files = downloadedFiles,
activeDownloads = activeDownloads,
onDelete = { dashcamVm.deleteDownloaded(it) }, onDelete = { dashcamVm.deleteDownloaded(it) },
onCancel = { dashcamVm.cancelDownload(it) },
onRefresh = { dashcamVm.scanDownloads() }, onRefresh = { dashcamVm.scanDownloads() },
) )
2 -> DualNetworkTestScreen( 2 -> DualNetworkTestScreen(