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 5f3da75..6c2409e 100644 --- a/android/app/src/main/java/com/yarisobd/app/DashcamViewModel.kt +++ b/android/app/src/main/java/com/yarisobd/app/DashcamViewModel.kt @@ -94,6 +94,10 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application) private val _uploadProgress = MutableStateFlow>(emptyMap()) val uploadProgress: StateFlow> = _uploadProgress.asStateFlow() + // Files already on server (filename set) + private val _uploadedNames = MutableStateFlow>(emptySet()) + val uploadedNames: StateFlow> = _uploadedNames.asStateFlow() + // USB dashcam state private val _dashcamFiles = MutableStateFlow>(emptyList()) val dashcamFiles: StateFlow> = _dashcamFiles.asStateFlow() @@ -117,6 +121,7 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application) init { loadVideos() scanDownloads() + _uploadedNames.value = CredentialStore.getUploaded(getApplication()) } fun loadVideos() { @@ -318,6 +323,8 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application) when (info.state) { WorkInfo.State.SUCCEEDED -> { + CredentialStore.markUploaded(getApplication(), file.name) + _uploadedNames.value = CredentialStore.getUploaded(getApplication()) viewModelScope.launch { _downloadEvents.emit("✓ Uploaded: ${file.name}") } _uploadProgress.value = _uploadProgress.value - file.name } 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 df65cb8..425f3c7 100644 --- a/android/app/src/main/java/com/yarisobd/app/DownloadsScreen.kt +++ b/android/app/src/main/java/com/yarisobd/app/DownloadsScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CloudUpload +import androidx.compose.material.icons.filled.CloudDone import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -61,6 +62,7 @@ fun DownloadsScreen( activeUploads: Map, deletingNames: Set, credentialsConfigured: Boolean, + uploadedNames: Set, onDelete: (DownloadedFile) -> Unit, onCancel: (ActiveDownload) -> Unit, onUpload: (DownloadedFile) -> Unit, @@ -214,7 +216,13 @@ fun DownloadsScreen( verticalAlignment = Alignment.CenterVertically ) { Column(Modifier.weight(1f)) { - Text(file.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis) + Row(verticalAlignment = Alignment.CenterVertically) { + Text(file.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f, fill = false)) + if (file.name in uploadedNames) { + Spacer(Modifier.width(6.dp)) + Icon(Icons.Filled.CloudDone, "On server", tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(16.dp)) + } + } Text(formatSize(file.size), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) } if (isDeleting) { 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 bce4a9c..e59b31d 100644 --- a/android/app/src/main/java/com/yarisobd/app/MainActivity.kt +++ b/android/app/src/main/java/com/yarisobd/app/MainActivity.kt @@ -62,6 +62,7 @@ fun YarisMainScreen(client: OkHttpClient) { val activeDownloads by dashcamVm.activeDownloads.collectAsState() val deletingNames by dashcamVm.deletingNames.collectAsState() val uploadProgress by dashcamVm.uploadProgress.collectAsState() + val uploadedNames by dashcamVm.uploadedNames.collectAsState() val dashcamFiles by dashcamVm.dashcamFiles.collectAsState() val dashcamTotalSize by dashcamVm.dashcamTotalSize.collectAsState() val dashcamStatus by dashcamVm.dashcamStatus.collectAsState() @@ -129,6 +130,7 @@ fun YarisMainScreen(client: OkHttpClient) { activeUploads = uploadProgress, deletingNames = deletingNames, credentialsConfigured = credentialsConfigured, + uploadedNames = uploadedNames, onDelete = { dashcamVm.deleteDownloaded(it) }, onCancel = { dashcamVm.cancelDownload(it) }, onUpload = { dashcamVm.uploadFile(it) }, diff --git a/android/app/src/main/java/com/yarisobd/app/data/CredentialStore.kt b/android/app/src/main/java/com/yarisobd/app/data/CredentialStore.kt index 36a2b09..dd25af5 100644 --- a/android/app/src/main/java/com/yarisobd/app/data/CredentialStore.kt +++ b/android/app/src/main/java/com/yarisobd/app/data/CredentialStore.kt @@ -7,6 +7,7 @@ object CredentialStore { private const val KEY_SERVER_URL = "server_url" private const val KEY_DEVICE_NAME = "device_name" private const val KEY_API_KEY = "api_key" + private const val KEY_UPLOADED = "uploaded_files" private fun prefs(context: Context) = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) @@ -25,4 +26,13 @@ object CredentialStore { .putString(KEY_API_KEY, apiKey) .apply() } + + fun markUploaded(context: Context, filename: String) { + val set = getUploaded(context).toMutableSet() + set.add(filename) + prefs(context).edit().putStringSet(KEY_UPLOADED, set).apply() + } + + fun getUploaded(context: Context): Set = + prefs(context).getStringSet(KEY_UPLOADED, emptySet()) ?: emptySet() }