diff --git a/.kotlin/sessions/kotlin-compiler-17775491840772701511.salive b/.kotlin/sessions/kotlin-compiler-17831070670597191982.salive similarity index 100% rename from .kotlin/sessions/kotlin-compiler-17775491840772701511.salive rename to .kotlin/sessions/kotlin-compiler-17831070670597191982.salive diff --git a/app/src/main/kotlin/com/syncflow/domain/sync/SyncEngine.kt b/app/src/main/kotlin/com/syncflow/domain/sync/SyncEngine.kt index 45ead7f..853bd1d 100644 --- a/app/src/main/kotlin/com/syncflow/domain/sync/SyncEngine.kt +++ b/app/src/main/kotlin/com/syncflow/domain/sync/SyncEngine.kt @@ -34,6 +34,16 @@ class SyncEngine @Inject constructor( @ApplicationContext private val context: Context, ) { suspend fun sync(pair: SyncPair, provider: CloudProvider): SyncResult { + if (!pair.localPath.startsWith("content://")) { + val canonical = runCatching { File(pair.localPath).canonicalPath }.getOrElse { pair.localPath } + if (canonical == "/storage/emulated/0") { + val msg = "Local folder is the storage root — Android blocks writes here. Edit the pair and select a subfolder." + syncPairDao.updateSyncResult(pair.id, Instant.now(), SyncStatus.FAILED, 0) + logEvent(pair.id, SyncEventType.SYNC_FAILED, null, msg, 0) + return SyncResult(failedFiles = 1, error = Exception(msg)) + } + } + syncPairDao.updateStatus(pair.id, SyncStatus.SYNCING) logEvent(pair.id, SyncEventType.SYNC_STARTED, null, null, 0) diff --git a/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt b/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt index 25d28b9..7dc21ef 100644 --- a/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt +++ b/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt @@ -263,22 +263,32 @@ fun LocalBrowserDialog( } // ── Select button ──────────────────────────────────────────── + val isStorageRoot = currentPath.absolutePath == STORAGE_ROOT.absolutePath Surface(tonalElevation = 4.dp, modifier = Modifier.fillMaxWidth()) { Column { - Button( - onClick = { onSelect(currentPath.absolutePath) }, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) - .height(52.dp), - shape = RoundedCornerShape(14.dp), - ) { - Icon(Icons.Default.CheckCircle, null, Modifier.size(20.dp)) - Spacer(Modifier.width(8.dp)) - Text("Select \"$currentFolderName\"", - style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold) - } - Spacer(Modifier.height(bottomInset)) + if (isStorageRoot) { + Text( + "Android blocks writes to the storage root — please open a subfolder first", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(horizontal = 20.dp, vertical = 8.dp), + ) + } + Button( + onClick = { onSelect(currentPath.absolutePath) }, + enabled = !isStorageRoot, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 4.dp, end = 16.dp, bottom = 12.dp) + .height(52.dp), + shape = RoundedCornerShape(14.dp), + ) { + Icon(Icons.Default.CheckCircle, null, Modifier.size(20.dp)) + Spacer(Modifier.width(8.dp)) + Text("Select \"$currentFolderName\"", + style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold) + } + Spacer(Modifier.height(bottomInset)) } } } diff --git a/version.properties b/version.properties index 22acddf..3a6c9f3 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -VERSION_NAME=1.0.52 -VERSION_CODE=53 +VERSION_NAME=1.0.53 +VERSION_CODE=54