media store monitoring: pause monitoring during bulk op
This commit is contained in:
parent
ea3d79afbe
commit
3d12825e68
8 changed files with 34 additions and 18 deletions
|
@ -49,7 +49,7 @@ class _AvesAppState extends State<AvesApp> {
|
|||
Future<void> _appSetup;
|
||||
final _mediaStoreSource = MediaStoreSource();
|
||||
final Debouncer _contentChangeDebouncer = Debouncer(delay: Durations.contentChangeDebounceDelay);
|
||||
final List<String> changedUris = [];
|
||||
final Set<String> 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<AvesApp> {
|
|||
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) {
|
||||
|
|
|
@ -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<CollectionFilter> 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<AvesEntry> _selection = {};
|
||||
|
|
|
@ -205,6 +205,16 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
|||
|
||||
Future<void> refreshMetadata(Set<AvesEntry> entries);
|
||||
|
||||
// monitoring
|
||||
|
||||
bool _monitoring = true;
|
||||
|
||||
void pauseMonitoring() => _monitoring = false;
|
||||
|
||||
void resumeMonitoring() => _monitoring = true;
|
||||
|
||||
bool get isMonitoring => _monitoring;
|
||||
|
||||
// filter summary
|
||||
|
||||
int count(CollectionFilter filter) {
|
||||
|
|
|
@ -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<List<String>> refreshUris(List<String> changedUris) async {
|
||||
final tempUris = <String>[];
|
||||
if (!_initialized) return tempUris;
|
||||
Future<Set<String>> refreshUris(Set<String> 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 = <String>{};
|
||||
final newEntries = <AvesEntry>{};
|
||||
for (final kv in uriByContentId.entries) {
|
||||
final contentId = kv.key;
|
||||
|
|
|
@ -124,10 +124,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> 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(
|
||||
|
|
|
@ -37,7 +37,6 @@ class _CollectionPageState extends State<CollectionPage> {
|
|||
body: WillPopScope(
|
||||
onWillPop: () {
|
||||
if (collection.isSelecting) {
|
||||
collection.clearSelection();
|
||||
collection.browse();
|
||||
return SynchronousFuture(false);
|
||||
}
|
||||
|
|
|
@ -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<MoveOpEvent>(
|
||||
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<ImageOpEvent>(
|
||||
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();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -87,6 +87,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
if (!await checkStoragePermission(context, selection)) return;
|
||||
|
||||
final selectionCount = selection.length;
|
||||
source.pauseMonitoring();
|
||||
showOpReport<ImageOpEvent>(
|
||||
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<MoveOpEvent>(
|
||||
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();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue