diff --git a/lib/main.dart b/lib/main.dart index 4cefeea66..92a0b6e65 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -49,7 +49,7 @@ class _AvesAppState extends State { Future _appSetup; final _mediaStoreSource = MediaStoreSource(); final Debouncer _contentChangeDebouncer = Debouncer(delay: Durations.contentChangeDebounceDelay); - final List changedUris = []; + final Set changedUris = {}; // observers are not registered when using the same list object with different items // the list itself needs to be reassigned @@ -193,7 +193,7 @@ class _AvesAppState extends State { if (uri != null) changedUris.add(uri); if (changedUris.isNotEmpty) { _contentChangeDebouncer(() async { - final todo = List.of(changedUris); + final todo = changedUris.toSet(); changedUris.clear(); final tempUris = await _mediaStoreSource.refreshUris(todo); if (tempUris.isNotEmpty) { diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index d20d9554e..109ba3318 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -16,7 +16,7 @@ import 'package:flutter/foundation.dart'; import 'enums.dart'; -class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSelectionMixin { +class CollectionLens with ChangeNotifier, CollectionActivityMixin { final CollectionSource source; final Set filters; EntryGroupFactor groupFactor; @@ -209,12 +209,15 @@ mixin CollectionActivityMixin { bool get isSelecting => _activityNotifier.value == Activity.select; - void browse() => _activityNotifier.value = Activity.browse; + void browse() { + clearSelection(); + _activityNotifier.value = Activity.browse; + } void select() => _activityNotifier.value = Activity.select; -} -mixin CollectionSelectionMixin on CollectionActivityMixin { + // selection + final AChangeNotifier selectionChangeNotifier = AChangeNotifier(); final Set _selection = {}; diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index 5f8280dc4..53bf70ed8 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -205,6 +205,16 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM Future refreshMetadata(Set entries); + // monitoring + + bool _monitoring = true; + + void pauseMonitoring() => _monitoring = false; + + void resumeMonitoring() => _monitoring = true; + + bool get isMonitoring => _monitoring; + // filter summary int count(CollectionFilter filter) { diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart index e3b69463e..8b098132b 100644 --- a/lib/model/source/media_store_source.dart +++ b/lib/model/source/media_store_source.dart @@ -107,12 +107,13 @@ class MediaStoreSource extends CollectionSource { ); } - // returns URIs that are in the Media Store but still being processed by their owner in a temporary location + // returns URIs to retry later. They could be URIs that are: + // 1) currently being processed during bulk move/deletion + // 2) registered in the Media Store but still being processed by their owner in a temporary location // For example, when taking a picture with a Galaxy S10e default camera app, querying the Media Store // sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg` - Future> refreshUris(List changedUris) async { - final tempUris = []; - if (!_initialized) return tempUris; + Future> refreshUris(Set changedUris) async { + if (!_initialized || !isMonitoring) return changedUris; final uriByContentId = Map.fromEntries(changedUris.map((uri) { if (uri == null) return null; @@ -129,6 +130,7 @@ class MediaStoreSource extends CollectionSource { obsoleteContentIds.forEach(uriByContentId.remove); // fetch new entries + final tempUris = {}; final newEntries = {}; for (final kv in uriByContentId.entries) { final contentId = kv.key; diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 8db5ed80a..4cc51796f 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -124,10 +124,7 @@ class _CollectionAppBarState extends State with SingleTickerPr onPressed = Scaffold.of(context).openDrawer; tooltip = MaterialLocalizations.of(context).openAppDrawerTooltip; } else if (collection.isSelecting) { - onPressed = () { - collection.clearSelection(); - collection.browse(); - }; + onPressed = collection.browse; tooltip = MaterialLocalizations.of(context).backButtonTooltip; } return IconButton( diff --git a/lib/widgets/collection/collection_page.dart b/lib/widgets/collection/collection_page.dart index 75e6ee911..99a6547bf 100644 --- a/lib/widgets/collection/collection_page.dart +++ b/lib/widgets/collection/collection_page.dart @@ -37,7 +37,6 @@ class _CollectionPageState extends State { body: WillPopScope( onWillPop: () { if (collection.isSelecting) { - collection.clearSelection(); collection.browse(); return SynchronousFuture(false); } diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 7bfaffe0a..2d41d0bce 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -55,7 +55,6 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware break; case CollectionAction.refreshMetadata: source.refreshMetadata(selection); - collection.clearSelection(); collection.browse(); break; default: @@ -87,6 +86,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware // while the move is ongoing, source monitoring may remove entries from itself and the favourites repo // so we save favourites beforehand, and will mark the moved entries as such after the move final favouriteEntries = todoEntries.where((entry) => entry.isFavourite).toSet(); + source.pauseMonitoring(); showOpReport( context: context, opStream: ImageFileService.move(todoEntries, copy: copy, destinationAlbum: destinationAlbum), @@ -108,8 +108,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware destinationAlbum: destinationAlbum, movedOps: movedOps, ); - collection.clearSelection(); collection.browse(); + source.resumeMonitoring(); }, ); } @@ -141,6 +141,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware if (!await checkStoragePermission(context, selection)) return; final selectionCount = selection.length; + source.pauseMonitoring(); showOpReport( context: context, opStream: ImageFileService.delete(selection), @@ -153,8 +154,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); } source.removeEntries(deletedUris); - collection.clearSelection(); collection.browse(); + source.resumeMonitoring(); }, ); } diff --git a/lib/widgets/filter_grids/common/chip_action_delegate.dart b/lib/widgets/filter_grids/common/chip_action_delegate.dart index b2fa1c7a1..86d1c318e 100644 --- a/lib/widgets/filter_grids/common/chip_action_delegate.dart +++ b/lib/widgets/filter_grids/common/chip_action_delegate.dart @@ -87,6 +87,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per if (!await checkStoragePermission(context, selection)) return; final selectionCount = selection.length; + source.pauseMonitoring(); showOpReport( context: context, opStream: ImageFileService.delete(selection), @@ -99,6 +100,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); } source.removeEntries(deletedUris); + source.resumeMonitoring(); }, ); } @@ -122,6 +124,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per // while the move is ongoing, source monitoring may remove entries from itself and the favourites repo // so we save favourites beforehand, and will mark the moved entries as such after the move final favouriteEntries = todoEntries.where((entry) => entry.isFavourite).toSet(); + source.pauseMonitoring(); showOpReport( context: context, opStream: ImageFileService.move(todoEntries, copy: false, destinationAlbum: destinationAlbum), @@ -148,6 +151,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per final newFilter = AlbumFilter(destinationAlbum, source.getUniqueAlbumName(destinationAlbum)); settings.pinnedFilters = settings.pinnedFilters..add(newFilter); } + source.resumeMonitoring(); }, ); }