From b523d77e1693662c21bb314910bce13280010242 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Wed, 12 Aug 2026 03:35:03 -0400 Subject: [PATCH] feat: cancel button on active downloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ActiveDownload now carries workerId (UUID string) for cancellation - cancelDownload() calls WorkManager.cancelWorkById() - Downloads tab shows ✕ button on in-progress download cards --- .../java/com/yarisobd/app/DashcamViewModel.kt | 27 ++++++++++++----- .../java/com/yarisobd/app/DownloadsScreen.kt | 29 +++++++++++++++---- .../java/com/yarisobd/app/MainActivity.kt | 3 +- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/android/app/src/main/java/com/yarisobd/app/DashcamViewModel.kt b/android/app/src/main/java/com/yarisobd/app/DashcamViewModel.kt index 477eb70..2686876 100644 --- a/android/app/src/main/java/com/yarisobd/app/DashcamViewModel.kt +++ b/android/app/src/main/java/com/yarisobd/app/DashcamViewModel.kt @@ -23,6 +23,7 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import kotlinx.serialization.json.Json import java.io.File +import java.util.UUID data class DownloadProgress( val path: String, @@ -37,8 +38,9 @@ data class DownloadedFile( ) data class ActiveDownload( + val workerId: String, // UUID string to cancel val filename: String, - val progress: Int, // 0-100 + val progress: Int, val done: Boolean, ) @@ -140,19 +142,19 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application) .build() val wm = WorkManager.getInstance(getApplication()) + val uuid = request.id + val workerId = uuid.toString() wm.enqueue(request) - // Observe progress - val workerId = request.id - _activeDownloads.value = _activeDownloads.value + (filename to ActiveDownload(filename, 0, false)) + _activeDownloads.value = _activeDownloads.value + (filename to ActiveDownload(workerId, filename, 0, false)) - wm.getWorkInfoByIdLiveData(workerId).observeForever { info -> + wm.getWorkInfoByIdLiveData(uuid).observeForever { info -> if (info == null) return@observeForever val progress = info.progress.getInt("pct", 0) 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) { WorkInfo.State.SUCCEEDED -> { @@ -169,7 +171,7 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application) } _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) { viewModelScope.launch { try { diff --git a/android/app/src/main/java/com/yarisobd/app/DownloadsScreen.kt b/android/app/src/main/java/com/yarisobd/app/DownloadsScreen.kt index 49b0582..77ced33 100644 --- a/android/app/src/main/java/com/yarisobd/app/DownloadsScreen.kt +++ b/android/app/src/main/java/com/yarisobd/app/DownloadsScreen.kt @@ -37,6 +37,7 @@ fun DownloadsScreen( files: List, activeDownloads: Map, onDelete: (DownloadedFile) -> Unit, + onCancel: (ActiveDownload) -> Unit, onRefresh: () -> Unit, ) { val context = LocalContext.current @@ -80,11 +81,29 @@ fun DownloadsScreen( ) ) { Column(modifier = Modifier.padding(12.dp)) { - Text( - text = if (dl.done) "✓ $filename" else "⏳ $filename", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + 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) { Spacer(modifier = Modifier.height(4.dp)) LinearProgressIndicator( diff --git a/android/app/src/main/java/com/yarisobd/app/MainActivity.kt b/android/app/src/main/java/com/yarisobd/app/MainActivity.kt index 2bb19ed..60045a9 100644 --- a/android/app/src/main/java/com/yarisobd/app/MainActivity.kt +++ b/android/app/src/main/java/com/yarisobd/app/MainActivity.kt @@ -105,9 +105,10 @@ fun YarisMainScreen(client: OkHttpClient) { onDelete = { dashcamVm.deleteVideo(it) }, ) 1 -> DownloadsScreen( - activeDownloads = activeDownloads, files = downloadedFiles, + activeDownloads = activeDownloads, onDelete = { dashcamVm.deleteDownloaded(it) }, + onCancel = { dashcamVm.cancelDownload(it) }, onRefresh = { dashcamVm.scanDownloads() }, ) 2 -> DualNetworkTestScreen(