Fix device sync: folder status badges, remove-deletes-local, multi-select, header summary
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:
2026-09-20 17:20:14 -04:00
co-authored by Claude Sonnet 5
parent ff5f04e9a1
commit 49ae712f5e
9 changed files with 273 additions and 51 deletions
+43 -12
View File
@@ -292,18 +292,15 @@ class _FilesViewState extends State<FilesView>
label: 'Rename',
onTap: () => _renameItem(selected.single),
),
if (selected.length == 1)
SelectionAction(
icon: sync.isPathSynced(selected.single.path)
? Icons.sync_rounded
: Icons.sync_outlined,
label: sync.isPathSynced(selected.single.path)
? 'Stop syncing to device'
: 'Sync to device',
onTap: () => sync.isPathSynced(selected.single.path)
? sync.removeSyncedPath(selected.single.path)
: sync.addSyncedPath(selected.single.path),
),
SelectionAction(
icon: selected.every((i) => sync.isPathSynced(i.path))
? Icons.sync_rounded
: Icons.sync_outlined,
label: selected.every((i) => sync.isPathSynced(i.path))
? 'Stop syncing to device'
: 'Sync to device',
onTap: () => _toggleSyncSelected(context, sync, selected),
),
if (selected.length == 1)
SelectionAction(
icon: Icons.info_outline_rounded,
@@ -807,6 +804,40 @@ class _FilesViewState extends State<FilesView>
_clearSelection();
}
/// Toggles device sync for every selected item at once - if they're all
/// already synced this stops syncing all of them (and deletes their local
/// mirrors, see `SyncStatusController.removeSyncedPath`), otherwise it
/// starts syncing whichever ones aren't synced yet.
void _toggleSyncSelected(
BuildContext context,
SyncStatusController sync,
List<NextcloudItem> items,
) {
final allSynced = items.every((i) => sync.isPathSynced(i.path));
if (allSynced) {
for (final item in items) {
sync.removeSyncedPath(item.path);
}
} else {
for (final item in items) {
if (!sync.isPathSynced(item.path)) {
sync.addSyncedPath(item.path, isFolder: item.isFolder);
}
}
}
_clearSelection();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
allSynced
? 'Removed ${items.length} item(s) from device sync'
: 'Syncing ${items.length} item(s) to this device',
),
behavior: SnackBarBehavior.floating,
),
);
}
/// Hands the whole batch off to `DownloadService.kt` (see its doc
/// comment) rather than downloading each item in Dart then prompting
/// `file_saver` per file - same reasoning as `UploadService`/