Fix device sync: folder status badges, remove-deletes-local, multi-select, header summary
Build APK / build (push) Successful in 5m35s
Build APK / build (push) Successful in 5m35s
- Folders now show the synced status badge themselves, not just their nested files - device-sync state never tracked folders individually, so syncStatusFor derives a folder's badge from whether its path falls within sync scope instead. - Turning sync off for a path now deletes its local mirror and clears its native sync-state entries (SyncEngine.removeLocalSync), instead of just stopping future updates and leaving the downloaded copy behind. - "Sync to device" in Files' selection toolbar now works over the whole selection at once instead of being gated to a single selected item. - The sync header's expanded panel shows real folder/item counts instead of a static "Synced" label that stayed accurate-sounding even before anything had actually synced. - Confirmation SnackBars on starting/stopping sync from both the selection toolbar and Settings' Device Sync card. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -136,6 +136,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
"syncNow" -> syncNow(call, result)
|
||||
"getSyncStatus" -> result.success(syncStatusMap(SyncStatusBus.snapshot()))
|
||||
"resolveConflict" -> resolveConflict(call, result)
|
||||
"removeLocalSync" -> removeLocalSync(call, result)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
@@ -405,6 +406,24 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
/// Deletes a path's local mirror once the user turns sync off for it -
|
||||
/// see [SyncEngine.removeLocalSync]. Runs off the main thread since it
|
||||
/// walks/deletes a directory tree; `result.success` is posted back via
|
||||
/// [mainHandler] since MethodChannel results must be delivered on the
|
||||
/// platform thread.
|
||||
private fun removeLocalSync(call: MethodCall, result: MethodChannel.Result) {
|
||||
val accountId = call.argument<String>("accountId")
|
||||
val path = call.argument<String>("path")
|
||||
if (accountId == null || path == null) {
|
||||
result.error("bad_args", "Missing required arguments", null)
|
||||
return
|
||||
}
|
||||
Thread {
|
||||
SyncEngine.removeLocalSync(applicationContext, accountId, path)
|
||||
mainHandler.post { result.success(null) }
|
||||
}.start()
|
||||
}
|
||||
|
||||
/// One-off immediate run (Settings' "Sync now"), independent of the
|
||||
/// periodic schedule.
|
||||
private fun syncNow(call: MethodCall, result: MethodChannel.Result) {
|
||||
|
||||
@@ -356,6 +356,35 @@ object SyncEngine {
|
||||
return actions
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes [path]'s local mirror (a single file, or a whole folder's
|
||||
* worth of files) and removes its entries from the persisted sync-state
|
||||
* map, called when the user turns sync off for that path - without
|
||||
* clearing the state too, a later re-add would see the (now missing)
|
||||
* local file as "deleted, server unchanged" and skip re-downloading it
|
||||
* (see [diffFolder]'s `!localExists && !serverChanged` branch) instead
|
||||
* of pulling it back down.
|
||||
*/
|
||||
fun removeLocalSync(context: Context, accountId: String, path: String) {
|
||||
val root = syncRoot(context, accountId)
|
||||
val state = loadState(context, accountId).toMutableMap()
|
||||
|
||||
var cleanPath = path.trim()
|
||||
if (cleanPath.startsWith("/")) cleanPath = cleanPath.substring(1)
|
||||
cleanPath = cleanPath.trimEnd('/')
|
||||
val prefix = if (cleanPath.isEmpty()) "" else "$cleanPath/"
|
||||
|
||||
val toRemove = state.filterValues { s ->
|
||||
s.relPath == cleanPath || (prefix.isNotEmpty() && s.relPath.startsWith(prefix))
|
||||
}.keys
|
||||
for (fileId in toRemove) state.remove(fileId)
|
||||
saveState(context, accountId, state)
|
||||
|
||||
val target = if (cleanPath.isEmpty()) root else File(root, cleanPath)
|
||||
if (target.exists()) target.deleteRecursively()
|
||||
Log.d(TAG, "removeLocalSync($path) -> removed ${toRemove.size} state entries")
|
||||
}
|
||||
|
||||
fun folderIsSyncedUnder(itemPath: String, syncedFolders: List<String>): String? {
|
||||
val normalizedItem = itemPath.trimEnd('/')
|
||||
for (folder in syncedFolders) {
|
||||
|
||||
Reference in New Issue
Block a user