feat: server-presence indicator on Downloads tab
- CredentialStore tracks uploaded filenames in prefs - UploadWorker success marks file as uploaded - Downloads tab shows green CloudDone icon next to files already on server - Upload button skipped for files already on server (dedup)
This commit is contained in:
@@ -94,6 +94,10 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
private val _uploadProgress = MutableStateFlow<Map<String, ActiveDownload>>(emptyMap())
|
private val _uploadProgress = MutableStateFlow<Map<String, ActiveDownload>>(emptyMap())
|
||||||
val uploadProgress: StateFlow<Map<String, ActiveDownload>> = _uploadProgress.asStateFlow()
|
val uploadProgress: StateFlow<Map<String, ActiveDownload>> = _uploadProgress.asStateFlow()
|
||||||
|
|
||||||
|
// Files already on server (filename set)
|
||||||
|
private val _uploadedNames = MutableStateFlow<Set<String>>(emptySet())
|
||||||
|
val uploadedNames: StateFlow<Set<String>> = _uploadedNames.asStateFlow()
|
||||||
|
|
||||||
// USB dashcam state
|
// USB dashcam state
|
||||||
private val _dashcamFiles = MutableStateFlow<List<DashcamFile>>(emptyList())
|
private val _dashcamFiles = MutableStateFlow<List<DashcamFile>>(emptyList())
|
||||||
val dashcamFiles: StateFlow<List<DashcamFile>> = _dashcamFiles.asStateFlow()
|
val dashcamFiles: StateFlow<List<DashcamFile>> = _dashcamFiles.asStateFlow()
|
||||||
@@ -117,6 +121,7 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
init {
|
init {
|
||||||
loadVideos()
|
loadVideos()
|
||||||
scanDownloads()
|
scanDownloads()
|
||||||
|
_uploadedNames.value = CredentialStore.getUploaded(getApplication())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadVideos() {
|
fun loadVideos() {
|
||||||
@@ -318,6 +323,8 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
|
|
||||||
when (info.state) {
|
when (info.state) {
|
||||||
WorkInfo.State.SUCCEEDED -> {
|
WorkInfo.State.SUCCEEDED -> {
|
||||||
|
CredentialStore.markUploaded(getApplication(), file.name)
|
||||||
|
_uploadedNames.value = CredentialStore.getUploaded(getApplication())
|
||||||
viewModelScope.launch { _downloadEvents.emit("✓ Uploaded: ${file.name}") }
|
viewModelScope.launch { _downloadEvents.emit("✓ Uploaded: ${file.name}") }
|
||||||
_uploadProgress.value = _uploadProgress.value - file.name
|
_uploadProgress.value = _uploadProgress.value - file.name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.CloudUpload
|
import androidx.compose.material.icons.filled.CloudUpload
|
||||||
|
import androidx.compose.material.icons.filled.CloudDone
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -61,6 +62,7 @@ fun DownloadsScreen(
|
|||||||
activeUploads: Map<String, ActiveDownload>,
|
activeUploads: Map<String, ActiveDownload>,
|
||||||
deletingNames: Set<String>,
|
deletingNames: Set<String>,
|
||||||
credentialsConfigured: Boolean,
|
credentialsConfigured: Boolean,
|
||||||
|
uploadedNames: Set<String>,
|
||||||
onDelete: (DownloadedFile) -> Unit,
|
onDelete: (DownloadedFile) -> Unit,
|
||||||
onCancel: (ActiveDownload) -> Unit,
|
onCancel: (ActiveDownload) -> Unit,
|
||||||
onUpload: (DownloadedFile) -> Unit,
|
onUpload: (DownloadedFile) -> Unit,
|
||||||
@@ -214,7 +216,13 @@ fun DownloadsScreen(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(Modifier.weight(1f)) {
|
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)
|
Text(formatSize(file.size), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
}
|
}
|
||||||
if (isDeleting) {
|
if (isDeleting) {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ fun YarisMainScreen(client: OkHttpClient) {
|
|||||||
val activeDownloads by dashcamVm.activeDownloads.collectAsState()
|
val activeDownloads by dashcamVm.activeDownloads.collectAsState()
|
||||||
val deletingNames by dashcamVm.deletingNames.collectAsState()
|
val deletingNames by dashcamVm.deletingNames.collectAsState()
|
||||||
val uploadProgress by dashcamVm.uploadProgress.collectAsState()
|
val uploadProgress by dashcamVm.uploadProgress.collectAsState()
|
||||||
|
val uploadedNames by dashcamVm.uploadedNames.collectAsState()
|
||||||
val dashcamFiles by dashcamVm.dashcamFiles.collectAsState()
|
val dashcamFiles by dashcamVm.dashcamFiles.collectAsState()
|
||||||
val dashcamTotalSize by dashcamVm.dashcamTotalSize.collectAsState()
|
val dashcamTotalSize by dashcamVm.dashcamTotalSize.collectAsState()
|
||||||
val dashcamStatus by dashcamVm.dashcamStatus.collectAsState()
|
val dashcamStatus by dashcamVm.dashcamStatus.collectAsState()
|
||||||
@@ -129,6 +130,7 @@ fun YarisMainScreen(client: OkHttpClient) {
|
|||||||
activeUploads = uploadProgress,
|
activeUploads = uploadProgress,
|
||||||
deletingNames = deletingNames,
|
deletingNames = deletingNames,
|
||||||
credentialsConfigured = credentialsConfigured,
|
credentialsConfigured = credentialsConfigured,
|
||||||
|
uploadedNames = uploadedNames,
|
||||||
onDelete = { dashcamVm.deleteDownloaded(it) },
|
onDelete = { dashcamVm.deleteDownloaded(it) },
|
||||||
onCancel = { dashcamVm.cancelDownload(it) },
|
onCancel = { dashcamVm.cancelDownload(it) },
|
||||||
onUpload = { dashcamVm.uploadFile(it) },
|
onUpload = { dashcamVm.uploadFile(it) },
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ object CredentialStore {
|
|||||||
private const val KEY_SERVER_URL = "server_url"
|
private const val KEY_SERVER_URL = "server_url"
|
||||||
private const val KEY_DEVICE_NAME = "device_name"
|
private const val KEY_DEVICE_NAME = "device_name"
|
||||||
private const val KEY_API_KEY = "api_key"
|
private const val KEY_API_KEY = "api_key"
|
||||||
|
private const val KEY_UPLOADED = "uploaded_files"
|
||||||
|
|
||||||
private fun prefs(context: Context) =
|
private fun prefs(context: Context) =
|
||||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||||
@@ -25,4 +26,13 @@ object CredentialStore {
|
|||||||
.putString(KEY_API_KEY, apiKey)
|
.putString(KEY_API_KEY, apiKey)
|
||||||
.apply()
|
.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<String> =
|
||||||
|
prefs(context).getStringSet(KEY_UPLOADED, emptySet()) ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user