From 651b5926dc49284fa6f8e6e48f40166d86ef885f Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 11 May 2025 21:57:38 +0200 Subject: [PATCH] #268 cover/pins/bookmarks sub to dynamics/groups; dynamics sub to groups; container filter mixin; debug: cover/dynamics dump; --- lib/model/app/dependencies.dart | 5 + lib/model/covers.dart | 54 ++++++++- lib/model/dynamic_albums.dart | 105 ++++++++++++++---- lib/model/filters/aspect_ratio.dart | 4 +- .../{covered => container}/album_group.dart | 15 ++- lib/model/filters/container/container.dart | 11 ++ .../{covered => container}/dynamic_album.dart | 27 ++++- .../{covered => container}/group_base.dart | 12 +- .../filters/{ => container}/set_and.dart | 24 +++- lib/model/filters/{ => container}/set_or.dart | 24 +++- lib/model/filters/coordinate.dart | 4 +- lib/model/filters/covered/location.dart | 4 +- lib/model/filters/covered/stored_album.dart | 6 +- lib/model/filters/covered/tag.dart | 4 +- lib/model/filters/date.dart | 4 +- lib/model/filters/favourite.dart | 2 +- lib/model/filters/filters.dart | 19 ++-- lib/model/filters/mime.dart | 4 +- lib/model/filters/missing.dart | 4 +- lib/model/filters/path.dart | 4 +- lib/model/filters/placeholder.dart | 2 +- lib/model/filters/query.dart | 6 +- lib/model/filters/rating.dart | 4 +- lib/model/filters/recent.dart | 4 +- lib/model/filters/trash.dart | 2 +- lib/model/filters/type.dart | 4 +- lib/model/filters/weekday.dart | 4 +- lib/model/grouping/common.dart | 28 ++++- lib/model/grouping/convert.dart | 4 +- lib/model/settings/modules/filter_grids.dart | 41 +++++++ lib/model/settings/modules/navigation.dart | 47 +++++++- lib/model/settings/settings.dart | 19 +++- lib/model/source/album.dart | 4 +- lib/model/source/collection_source.dart | 2 +- lib/widgets/collection/app_bar.dart | 4 +- .../collection/entry_set_action_delegate.dart | 4 +- lib/widgets/debug/database.dart | 20 +++- .../dialogs/pick_dialogs/album_pick_page.dart | 4 +- lib/widgets/filter_grids/albums_page.dart | 4 +- .../common/action_delegates/album_set.dart | 65 ++--------- .../common/action_delegates/chip.dart | 4 +- .../common/action_delegates/chip_set.dart | 2 +- .../common/covered_filter_chip.dart | 4 +- .../filter_grids/common/filter_grid_page.dart | 2 +- .../filter_grids/common/filter_nav_page.dart | 2 +- .../filter_grids/common/list_details.dart | 4 +- lib/widgets/navigation/drawer/app_drawer.dart | 4 +- .../drawer/collection_nav_tile.dart | 2 +- lib/widgets/navigation/tv_rail.dart | 2 +- lib/widgets/search/search_delegate.dart | 6 +- lib/widgets/settings/navigation/drawer.dart | 2 +- .../navigation/drawer_tab_albums.dart | 2 +- pubspec.lock | 2 +- pubspec.yaml | 1 + test/model/filters_test.dart | 8 +- test/model/grouping/common_test.dart | 6 +- test/model/grouping/convert_test.dart | 6 +- 57 files changed, 478 insertions(+), 189 deletions(-) rename lib/model/filters/{covered => container}/album_group.dart (78%) create mode 100644 lib/model/filters/container/container.dart rename lib/model/filters/{covered => container}/dynamic_album.dart (60%) rename lib/model/filters/{covered => container}/group_base.dart (73%) rename lib/model/filters/{ => container}/set_and.dart (80%) rename lib/model/filters/{ => container}/set_or.dart (80%) diff --git a/lib/model/app/dependencies.dart b/lib/model/app/dependencies.dart index 35211c148..cf408038b 100644 --- a/lib/model/app/dependencies.dart +++ b/lib/model/app/dependencies.dart @@ -374,6 +374,11 @@ class Dependencies { license: bsd3, sourceUrl: 'https://github.com/dart-lang/stack_trace', ), + Dependency( + name: 'Synchronized', + license: mit, + sourceUrl: 'https://github.com/tekartik/synchronized.dart/tree/master/synchronized', + ), Dependency( name: 'Vector Math', license: '$zlib, $bsd3', diff --git a/lib/model/covers.dart b/lib/model/covers.dart index e7acf2d2d..a99308f35 100644 --- a/lib/model/covers.dart +++ b/lib/model/covers.dart @@ -1,10 +1,13 @@ import 'dart:async'; import 'package:aves/model/app_inventory.dart'; +import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/services/common/services.dart'; @@ -15,12 +18,16 @@ import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; +import 'package:synchronized/synchronized.dart'; final Covers covers = Covers._private(); typedef CoverProps = (int? entryId, String? packageName, Color? color); class Covers { + final List _subscriptions = []; + final _lock = Lock(); + final StreamController?> _entryChangeStreamController = StreamController.broadcast(); final StreamController?> _packageChangeStreamController = StreamController.broadcast(); final StreamController?> _colorChangeStreamController = StreamController.broadcast(); @@ -37,6 +44,8 @@ class Covers { Future init() async { _rows = await localMediaDb.loadAllCovers(); + _subscriptions.add(dynamicAlbums.eventBus.on().listen((e) => _updateCoveredDynamicAlbums(e.changes))); + _subscriptions.add(albumGrouping.eventBus.on().listen((e) => _updateCoveredGroup(e.oldGroupUri, e.newGroupUri))); } int get count => _rows.length; @@ -172,6 +181,49 @@ class Covers { return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath); } + Future _updateCoveredDynamicAlbums(Map changes) async { + await _lock.synchronized(() async { + await Future.forEach(changes.entries, (kv) async { + final oldFilter = kv.key; + final newFilter = kv.value; + + final cover = await covers.remove(oldFilter, notify: false); + if (cover != null && newFilter != null) { + await covers.set( + filter: newFilter, + entryId: cover.$1, + packageName: cover.$2, + color: cover.$3, + notify: true, + ); + } + }); + }); + } + + Future _updateCoveredGroup(Uri oldGroupUri, Uri newGroupUri) async { + await _lock.synchronized(() async { + final grouping = FilterGrouping.forUri(oldGroupUri); + if (grouping != null) { + final oldFilter = grouping.uriToFilter(oldGroupUri); + final newFilter = grouping.uriToFilter(newGroupUri); + + if (oldFilter != null) { + final cover = await covers.remove(oldFilter, notify: false); + if (cover != null && newFilter != null) { + await covers.set( + filter: newFilter, + entryId: cover.$1, + packageName: cover.$2, + color: cover.$3, + notify: true, + ); + } + } + } + }); + } + // import/export List>? export(CollectionSource source) { diff --git a/lib/model/dynamic_albums.dart b/lib/model/dynamic_albums.dart index 333308758..2313f0773 100644 --- a/lib/model/dynamic_albums.dart +++ b/lib/model/dynamic_albums.dart @@ -1,17 +1,29 @@ -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'dart:async'; + +import 'package:aves/model/filters/container/container.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; +import 'package:aves/model/filters/container/group_base.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/services/common/services.dart'; import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; +import 'package:event_bus/event_bus.dart'; import 'package:flutter/foundation.dart'; +import 'package:synchronized/synchronized.dart'; final DynamicAlbums dynamicAlbums = DynamicAlbums._private(); class DynamicAlbums with ChangeNotifier { + final List _subscriptions = []; + final _lock = Lock(); Set _rows = {}; + final EventBus eventBus = EventBus(); + DynamicAlbums._private() { if (kFlutterMemoryAllocationsEnabled) ChangeNotifier.maybeDispatchObjectCreation(this); + _subscriptions.add(albumGrouping.eventBus.on().listen((e) => _onGroupUriChanged(e.oldGroupUri, e.newGroupUri))); } Future init() async { @@ -22,38 +34,86 @@ class DynamicAlbums with ChangeNotifier { Set get all => Set.unmodifiable(_rows); - Future add(DynamicAlbumFilter filter) async { - await localMediaDb.addDynamicAlbums({DynamicAlbumRow(name: filter.name, filter: filter.filter)}); - _rows.add(filter); + Future _doAdd(Set? filters) async { + if (filters == null || filters.isEmpty) return; + await localMediaDb.addDynamicAlbums(filters.map((v) => DynamicAlbumRow(name: v.name, filter: v.filter)).toSet()); + _rows.addAll(filters); + } - notifyListeners(); + Future _doRemove(Set? names) async { + if (names == null || names.isEmpty) return; + await localMediaDb.removeDynamicAlbums(names); + _rows.removeWhere((v) => names.contains(v.name)); + } + + Future add(DynamicAlbumFilter filter) async { + await _lock.synchronized(() async { + await _doAdd({filter}); + notifyListeners(); + }); } Future remove(Set filters) async { - await localMediaDb.removeDynamicAlbums(filters.map((filter) => filter.name).toSet()); - _rows.removeAll(filters); + await _lock.synchronized(() async { + await _doRemove(filters.map((filter) => filter.name).toSet()); + notifyListeners(); + }); + } - notifyListeners(); + Future rename(DynamicAlbumFilter oldFilter, String newName) async { + await _lock.synchronized(() async { + final newFilter = DynamicAlbumFilter(newName, oldFilter.filter); + await _doRemove({oldFilter.name}); + await _doAdd({newFilter}); + notifyListeners(); + eventBus.fire(DynamicAlbumChangedEvent({oldFilter: newFilter})); + }); + } + + Future update(Map changes) async { + await _lock.synchronized(() async { + final oldFilterNames = changes.keys.map((v) => v.name).toSet(); + final newFilters = changes.values.nonNulls.toSet(); + await _doRemove(oldFilterNames); + await _doAdd(newFilters); + notifyListeners(); + eventBus.fire(DynamicAlbumChangedEvent(changes)); + }); } Future clear() async { - await localMediaDb.clearDynamicAlbums(); - _rows.clear(); - - notifyListeners(); - } - - Future rename(DynamicAlbumFilter filter, String newName) async { - await localMediaDb.removeDynamicAlbums({filter.name}); - _rows.remove(filter); - - await add(DynamicAlbumFilter(newName, filter.filter)); + await _lock.synchronized(() async { + await localMediaDb.clearDynamicAlbums(); + _rows.clear(); + notifyListeners(); + }); } DynamicAlbumFilter? get(String name) => _rows.firstWhereOrNull((row) => row.name == name); bool contains(String? name) => name != null && get(name) != null; + Future _onGroupUriChanged(Uri oldGroupUri, Uri newGroupUri) async { + bool isOldGroupFilter(CollectionFilter filter) => filter is GroupBaseFilter && filter.uri == oldGroupUri; + final oldDynamicAlbumFilters = _rows.where((v) => v.containsFilter(isOldGroupFilter)).toSet(); + if (oldDynamicAlbumFilters.isEmpty) return; + + final grouping = FilterGrouping.forUri(oldGroupUri); + final newGroupFilter = grouping?.uriToFilter(newGroupUri); + CollectionFilter? transformer(v) { + if (isOldGroupFilter(v)) return v.reversed ? newGroupFilter?.reverse() : newGroupFilter; + if (v is ContainerFilter) return v.replaceFilters(transformer); + return v; + } + + final newFilters = {}; + oldDynamicAlbumFilters.forEach((oldFilter) { + final newFilter = oldFilter.replaceFilters(transformer); + newFilters[oldFilter] = newFilter; + }); + await update(newFilters); + } + // import/export List? export() { @@ -107,3 +167,10 @@ class DynamicAlbumRow extends Equatable { 'filter': filter.toJson(), }; } + +@immutable +class DynamicAlbumChangedEvent { + final Map changes; + + const DynamicAlbumChangedEvent(this.changes); +} diff --git a/lib/model/filters/aspect_ratio.dart b/lib/model/filters/aspect_ratio.dart index 5fcf6933f..c79146411 100644 --- a/lib/model/filters/aspect_ratio.dart +++ b/lib/model/filters/aspect_ratio.dart @@ -9,7 +9,7 @@ class AspectRatioFilter extends CollectionFilter { final double threshold; final String op; - late final EntryFilter _test; + late final EntryPredicate _test; static final landscape = AspectRatioFilter(1, QueryFilter.opGreater); static final portrait = AspectRatioFilter(1, QueryFilter.opLower); @@ -45,7 +45,7 @@ class AspectRatioFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => true; diff --git a/lib/model/filters/covered/album_group.dart b/lib/model/filters/container/album_group.dart similarity index 78% rename from lib/model/filters/covered/album_group.dart rename to lib/model/filters/container/album_group.dart index 2d70fdfed..7ee6fac1d 100644 --- a/lib/model/filters/covered/album_group.dart +++ b/lib/model/filters/container/album_group.dart @@ -1,6 +1,6 @@ -import 'package:aves/model/filters/covered/group_base.dart'; +import 'package:aves/model/filters/container/group_base.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/filters/container/set_or.dart'; mixin AlbumBaseFilter on CollectionFilter {} @@ -46,4 +46,15 @@ class AlbumGroupFilter extends GroupBaseFilter with AlbumBaseFilter { @override String get key => '$type-$reversed-$uri'; + + // container + + @override + AlbumGroupFilter? replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement) { + return AlbumGroupFilter( + uri, + filter.replaceFilters(toElement), + reversed: reversed, + ); + } } diff --git a/lib/model/filters/container/container.dart b/lib/model/filters/container/container.dart new file mode 100644 index 000000000..08f790160 --- /dev/null +++ b/lib/model/filters/container/container.dart @@ -0,0 +1,11 @@ +import 'package:aves/model/filters/filters.dart'; + +mixin ContainerFilter on CollectionFilter { + Set get innerFilters; + + bool containsFilter(CollectionFilterPredicate test) { + return innerFilters.any(test) || innerFilters.whereType().any((v) => v.containsFilter(test)); + } + + T? replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement); +} diff --git a/lib/model/filters/covered/dynamic_album.dart b/lib/model/filters/container/dynamic_album.dart similarity index 60% rename from lib/model/filters/covered/dynamic_album.dart rename to lib/model/filters/container/dynamic_album.dart index c0f10e65a..b5fa5c38b 100644 --- a/lib/model/filters/covered/dynamic_album.dart +++ b/lib/model/filters/container/dynamic_album.dart @@ -1,10 +1,14 @@ -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/container.dart'; import 'package:aves/model/filters/covered/covered.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/icons.dart'; import 'package:flutter/widgets.dart'; -class DynamicAlbumFilter extends CollectionFilter with CoveredFilter, AlbumBaseFilter { +// a dynamic album can act as: +// - an alias, when inner filter is a simple filter, +// - a combination, when inner filter is a container. +class DynamicAlbumFilter extends CollectionFilter with ContainerFilter, CoveredFilter, AlbumBaseFilter { static const type = 'dynamic_album'; final String name; @@ -35,7 +39,7 @@ class DynamicAlbumFilter extends CollectionFilter with CoveredFilter, AlbumBaseF }; @override - EntryFilter get positiveTest => filter.test; + EntryPredicate get positiveTest => filter.test; @override bool get exclusiveProp => false; @@ -53,4 +57,21 @@ class DynamicAlbumFilter extends CollectionFilter with CoveredFilter, AlbumBaseF @override String get key => '$type-$reversed-$name'; + + // container + + @override + Set get innerFilters => {filter}; + + @override + DynamicAlbumFilter? replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement) { + final newFilter = toElement(filter); + return newFilter != null + ? DynamicAlbumFilter( + name, + newFilter, + reversed: reversed, + ) + : null; + } } diff --git a/lib/model/filters/covered/group_base.dart b/lib/model/filters/container/group_base.dart similarity index 73% rename from lib/model/filters/covered/group_base.dart rename to lib/model/filters/container/group_base.dart index a8b32d418..a4513336d 100644 --- a/lib/model/filters/covered/group_base.dart +++ b/lib/model/filters/container/group_base.dart @@ -1,11 +1,12 @@ +import 'package:aves/model/filters/container/container.dart'; +import 'package:aves/model/filters/container/set_or.dart'; import 'package:aves/model/filters/covered/covered.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/set_or.dart'; import 'package:aves/model/grouping/common.dart'; import 'package:aves/theme/icons.dart'; import 'package:flutter/widgets.dart'; -abstract class GroupBaseFilter extends CollectionFilter with CoveredFilter { +abstract class GroupBaseFilter extends CollectionFilter with ContainerFilter, CoveredFilter { final Uri uri; final SetOrFilter filter; late final String _name; @@ -20,7 +21,7 @@ abstract class GroupBaseFilter extends CollectionFilter with CoveredFilter { } @override - EntryFilter get positiveTest => filter.test; + EntryPredicate get positiveTest => filter.test; @override bool get exclusiveProp => false; @@ -32,4 +33,9 @@ abstract class GroupBaseFilter extends CollectionFilter with CoveredFilter { Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) { return allowGenericIcon ? Icon(AIcons.group, size: size) : null; } + + // container + + @override + Set get innerFilters => {filter}; } diff --git a/lib/model/filters/set_and.dart b/lib/model/filters/container/set_and.dart similarity index 80% rename from lib/model/filters/set_and.dart rename to lib/model/filters/container/set_and.dart index 6254f45f2..396f5d2ea 100644 --- a/lib/model/filters/set_and.dart +++ b/lib/model/filters/container/set_and.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/filters/container/container.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/covered/location.dart'; @@ -5,12 +6,14 @@ import 'package:aves/theme/icons.dart'; import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; -class SetAndFilter extends CollectionFilter { +// `AND` op for multiple filters +// can handle containing no filters +class SetAndFilter extends CollectionFilter with ContainerFilter { static const type = 'and'; late final List _filters; - late final EntryFilter _test; + late final EntryPredicate _test; late final IconData? _genericIcon; @override @@ -18,8 +21,6 @@ class SetAndFilter extends CollectionFilter { CollectionFilter? get _first => _filters.firstOrNull; - Set get innerFilters => _filters.toSet(); - SetAndFilter(Set filters, {super.reversed = false}) { _filters = filters.toList().sorted(); _test = (entry) => _filters.every((v) => v.test(entry)); @@ -53,7 +54,7 @@ class SetAndFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; @@ -74,4 +75,17 @@ class SetAndFilter extends CollectionFilter { @override String get key => '$type-$reversed-${_filters.map((v) => v.key)}'; + + // container + + @override + Set get innerFilters => _filters.toSet(); + + @override + SetAndFilter replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement) { + return SetAndFilter( + _filters.map((v) => toElement(v)).nonNulls.toSet(), + reversed: reversed, + ); + } } diff --git a/lib/model/filters/set_or.dart b/lib/model/filters/container/set_or.dart similarity index 80% rename from lib/model/filters/set_or.dart rename to lib/model/filters/container/set_or.dart index 3278799c3..10d1f74de 100644 --- a/lib/model/filters/set_or.dart +++ b/lib/model/filters/container/set_or.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/filters/container/container.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; @@ -5,12 +6,14 @@ import 'package:aves/theme/icons.dart'; import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; -class SetOrFilter extends CollectionFilter { +// `OR` op for multiple filters +// can handle containing no filters +class SetOrFilter extends CollectionFilter with ContainerFilter { static const type = 'or'; late final List _filters; - late final EntryFilter _test; + late final EntryPredicate _test; late final IconData? _genericIcon; @override @@ -18,8 +21,6 @@ class SetOrFilter extends CollectionFilter { CollectionFilter? get _first => _filters.firstOrNull; - Set get innerFilters => _filters.toSet(); - SetOrFilter(Set filters, {super.reversed = false}) { _filters = filters.toList().sorted(); _test = (entry) => _filters.any((v) => v.test(entry)); @@ -53,7 +54,7 @@ class SetOrFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; @@ -74,4 +75,17 @@ class SetOrFilter extends CollectionFilter { @override String get key => '$type-$reversed-${_filters.map((v) => v.key)}'; + + // container + + @override + Set get innerFilters => _filters.toSet(); + + @override + SetOrFilter replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement) { + return SetOrFilter( + _filters.map((v) => toElement(v)).nonNulls.toSet(), + reversed: reversed, + ); + } } diff --git a/lib/model/filters/coordinate.dart b/lib/model/filters/coordinate.dart index 3eea4e5d8..71edeec41 100644 --- a/lib/model/filters/coordinate.dart +++ b/lib/model/filters/coordinate.dart @@ -15,7 +15,7 @@ class CoordinateFilter extends CollectionFilter { final LatLng sw; final LatLng ne; final bool minuteSecondPadding; - late final EntryFilter _test; + late final EntryPredicate _test; @override List get props => [sw, ne, reversed]; @@ -41,7 +41,7 @@ class CoordinateFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; String _formatBounds(String Function(LatLng latLng) s) => '${s(ne)}\n${s(sw)}'; diff --git a/lib/model/filters/covered/location.dart b/lib/model/filters/covered/location.dart index 701a3f8b4..4c5af65ae 100644 --- a/lib/model/filters/covered/location.dart +++ b/lib/model/filters/covered/location.dart @@ -14,7 +14,7 @@ class LocationFilter extends CollectionFilter with CoveredFilter { final LocationLevel level; late final String _location; late final String? _code; - late final EntryFilter _test; + late final EntryPredicate _test; static final unlocated = LocationFilter(LocationLevel.place, ''); static final located = unlocated.reverse(); @@ -78,7 +78,7 @@ class LocationFilter extends CollectionFilter with CoveredFilter { String get place => _location; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => true; diff --git a/lib/model/filters/covered/stored_album.dart b/lib/model/filters/covered/stored_album.dart index 97b4f4bf9..127bc4bca 100644 --- a/lib/model/filters/covered/stored_album.dart +++ b/lib/model/filters/covered/stored_album.dart @@ -1,5 +1,5 @@ import 'package:aves/model/covers.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; import 'package:aves/model/filters/covered/covered.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/vaults/vaults.dart'; @@ -18,7 +18,7 @@ class StoredAlbumFilter extends CollectionFilter with CoveredFilter, AlbumBaseFi final String album; final String? displayName; - late final EntryFilter _test; + late final EntryPredicate _test; // do not include contextual `displayName` to `props` @override @@ -45,7 +45,7 @@ class StoredAlbumFilter extends CollectionFilter with CoveredFilter, AlbumBaseFi }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => true; diff --git a/lib/model/filters/covered/tag.dart b/lib/model/filters/covered/tag.dart index 51bffbdaa..954d5d7a6 100644 --- a/lib/model/filters/covered/tag.dart +++ b/lib/model/filters/covered/tag.dart @@ -8,7 +8,7 @@ class TagFilter extends CollectionFilter with CoveredFilter { static const type = 'tag'; final String tag; - late final EntryFilter _test; + late final EntryPredicate _test; @override List get props => [tag, reversed]; @@ -36,7 +36,7 @@ class TagFilter extends CollectionFilter with CoveredFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; diff --git a/lib/model/filters/date.dart b/lib/model/filters/date.dart index d73b8ab59..d01f1844f 100644 --- a/lib/model/filters/date.dart +++ b/lib/model/filters/date.dart @@ -13,7 +13,7 @@ class DateFilter extends CollectionFilter { final DateLevel level; late final DateTime? date; late final DateTime _effectiveDate; - late final EntryFilter _test; + late final EntryPredicate _test; static final onThisDay = DateFilter(DateLevel.md, null); @@ -63,7 +63,7 @@ class DateFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => true; diff --git a/lib/model/filters/favourite.dart b/lib/model/filters/favourite.dart index fc8855260..1538fdb2d 100644 --- a/lib/model/filters/favourite.dart +++ b/lib/model/filters/favourite.dart @@ -33,7 +33,7 @@ class FavouriteFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index bc3db189f..5aa653568 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/aspect_ratio.dart'; import 'package:aves/model/filters/coordinate.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; @@ -17,8 +17,8 @@ import 'package:aves/model/filters/placeholder.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/recent.dart'; -import 'package:aves/model/filters/set_and.dart'; -import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/filters/container/set_and.dart'; +import 'package:aves/model/filters/container/set_or.dart'; import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/model/filters/weekday.dart'; @@ -128,9 +128,9 @@ abstract class CollectionFilter extends Equatable implements Comparable jsonEncode(toMap()); - EntryFilter get positiveTest; + EntryPredicate get positiveTest; - EntryFilter get test => reversed ? (v) => !positiveTest(v) : positiveTest; + EntryPredicate get test => reversed ? (v) => !positiveTest(v) : positiveTest; CollectionFilter reverse() => _fromMap(toMap()..['reversed'] = !reversed)!; @@ -150,7 +150,7 @@ abstract class CollectionFilter extends Equatable implements Comparable getLabel(context); - bool match(BuildContext context, String query) => getLabel(context).toUpperCase().contains(query); + bool matchLabel(BuildContext context, String query) => getLabel(context).toUpperCase().contains(query); Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => null; @@ -185,7 +185,8 @@ class FilterGridItem with EquatableMixin { const FilterGridItem(this.filter, this.entry); } -typedef EntryFilter = bool Function(AvesEntry); +typedef EntryPredicate = bool Function(AvesEntry entry); +typedef CollectionFilterPredicate = bool Function(CollectionFilter filter); abstract class DummyCollectionFilter extends CollectionFilter { const DummyCollectionFilter({required super.reversed}); @@ -200,7 +201,7 @@ abstract class DummyCollectionFilter extends CollectionFilter { String get key => throw UnimplementedError(); @override - EntryFilter get positiveTest => throw UnimplementedError(); + EntryPredicate get positiveTest => throw UnimplementedError(); @override List get props => throw UnimplementedError(); diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart index 120b6fab8..e96e6abea 100644 --- a/lib/model/filters/mime.dart +++ b/lib/model/filters/mime.dart @@ -16,7 +16,7 @@ class MimeFilter extends CollectionFilter { final String mime; late final String _label; late final IconData _icon; - late final EntryFilter _test; + late final EntryPredicate _test; static final image = MimeFilter(MimeTypes.anyImage); static final video = MimeFilter(MimeTypes.anyVideo); @@ -58,7 +58,7 @@ class MimeFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => true; diff --git a/lib/model/filters/missing.dart b/lib/model/filters/missing.dart index 1f9bc6bf1..6866435cd 100644 --- a/lib/model/filters/missing.dart +++ b/lib/model/filters/missing.dart @@ -12,7 +12,7 @@ class MissingFilter extends CollectionFilter { final String metadataType; late final IconData _icon; - late final EntryFilter _test; + late final EntryPredicate _test; static final date = MissingFilter._private(_date); static final fineAddress = MissingFilter._private(_fineAddress); @@ -50,7 +50,7 @@ class MissingFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; diff --git a/lib/model/filters/path.dart b/lib/model/filters/path.dart index bb0adea54..7cee212c2 100644 --- a/lib/model/filters/path.dart +++ b/lib/model/filters/path.dart @@ -14,7 +14,7 @@ class PathFilter extends CollectionFilter { // without trailing separator late final String _rootAlbum; - late final EntryFilter _test; + late final EntryPredicate _test; @override List get props => [path, reversed]; @@ -45,7 +45,7 @@ class PathFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => true; diff --git a/lib/model/filters/placeholder.dart b/lib/model/filters/placeholder.dart index 4f6145f7f..b217a5909 100644 --- a/lib/model/filters/placeholder.dart +++ b/lib/model/filters/placeholder.dart @@ -76,7 +76,7 @@ class PlaceholderFilter extends CollectionFilter { } @override - EntryFilter get positiveTest => (entry) => throw Exception('this is not a test'); + EntryPredicate get positiveTest => (entry) => throw Exception('this is not a test'); @override bool get exclusiveProp => false; diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index 11897bc25..80e8d7835 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -14,7 +14,7 @@ class QueryFilter extends CollectionFilter { final String query; final bool colorful, live; - late final EntryFilter _test; + late final EntryPredicate _test; @override List get props => [query, live, reversed]; @@ -74,7 +74,7 @@ class QueryFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; @@ -101,7 +101,7 @@ class QueryFilter extends CollectionFilter { @override String get key => '$type-$reversed-$query'; - EntryFilter? fieldTest(String upQuery) { + EntryPredicate? fieldTest(String upQuery) { var match = _fieldPattern.firstMatch(upQuery); if (match == null) return null; diff --git a/lib/model/filters/rating.dart b/lib/model/filters/rating.dart index ac296d365..b907442c2 100644 --- a/lib/model/filters/rating.dart +++ b/lib/model/filters/rating.dart @@ -9,7 +9,7 @@ class RatingFilter extends CollectionFilter { final int rating; final String op; - late final EntryFilter _test; + late final EntryPredicate _test; static const opEqual = '='; static const opOrLower = '<='; @@ -49,7 +49,7 @@ class RatingFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => true; diff --git a/lib/model/filters/recent.dart b/lib/model/filters/recent.dart index 57d1b1e52..d39a08b55 100644 --- a/lib/model/filters/recent.dart +++ b/lib/model/filters/recent.dart @@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart'; class RecentlyAddedFilter extends CollectionFilter { static const type = 'recently_added'; - static late EntryFilter _test; + static late EntryPredicate _test; static final instance = RecentlyAddedFilter._private(); static final instanceReversed = RecentlyAddedFilter._private(reversed: true); @@ -39,7 +39,7 @@ class RecentlyAddedFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; diff --git a/lib/model/filters/trash.dart b/lib/model/filters/trash.dart index 095a058f9..f250bfa69 100644 --- a/lib/model/filters/trash.dart +++ b/lib/model/filters/trash.dart @@ -29,7 +29,7 @@ class TrashFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart index 89e77762d..9d1e7476f 100644 --- a/lib/model/filters/type.dart +++ b/lib/model/filters/type.dart @@ -21,7 +21,7 @@ class TypeFilter extends CollectionFilter { final String itemType; late final IconData _icon; - late final EntryFilter _test; + late final EntryPredicate _test; static final animated = TypeFilter._private(_animated); static final geotiff = TypeFilter._private(_geotiff); @@ -75,7 +75,7 @@ class TypeFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => false; diff --git a/lib/model/filters/weekday.dart b/lib/model/filters/weekday.dart index 4a150c88c..406761416 100644 --- a/lib/model/filters/weekday.dart +++ b/lib/model/filters/weekday.dart @@ -8,7 +8,7 @@ class WeekDayFilter extends CollectionFilter { static const type = 'weekday'; late final int weekday; - late final EntryFilter _test; + late final EntryPredicate _test; @override List get props => [weekday, reversed]; @@ -32,7 +32,7 @@ class WeekDayFilter extends CollectionFilter { }; @override - EntryFilter get positiveTest => _test; + EntryPredicate get positiveTest => _test; @override bool get exclusiveProp => true; diff --git a/lib/model/grouping/common.dart b/lib/model/grouping/common.dart index b43d28667..cc4af8bc5 100644 --- a/lib/model/grouping/common.dart +++ b/lib/model/grouping/common.dart @@ -1,13 +1,14 @@ import 'dart:convert'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/group_base.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/group_base.dart'; +import 'package:aves/model/filters/container/set_or.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/set_or.dart'; import 'package:aves/model/grouping/convert.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/collection_utils.dart'; import 'package:collection/collection.dart'; +import 'package:event_bus/event_bus.dart'; import 'package:flutter/foundation.dart'; final FilterGrouping albumGrouping = FilterGrouping._private(FilterGrouping.hostAlbums, AlbumGroupFilter.new); @@ -22,6 +23,8 @@ class FilterGrouping with ChangeNotifier { static const _groupPath = '/group'; static const _groupPathParamKey = 'path'; + final EventBus eventBus = EventBus(); + final String _host; final T Function(Uri uri, SetOrFilter filter) _createGroupFilter; final Map> _groups = {}; @@ -30,6 +33,15 @@ class FilterGrouping with ChangeNotifier { if (kFlutterMemoryAllocationsEnabled) ChangeNotifier.maybeDispatchObjectCreation(this); } + static FilterGrouping? forUri(Uri uri) { + switch (uri.host) { + case hostAlbums: + return albumGrouping; + default: + return null; + } + } + void clear() => _groups.clear(); // TODO TLAD [nested] invalidate summary for derived filters (parent groups, dynamic albums) @@ -52,6 +64,7 @@ class FilterGrouping with ChangeNotifier { // local copy to prevent concurrent modification addToGroup(Set.of(childrenUris), newUri); } + eventBus.fire(GroupUriChangedEvent(oldUri, newUri)); } bool get isNotEmpty => _groups.isNotEmpty; @@ -147,6 +160,7 @@ class FilterGrouping with ChangeNotifier { } } + eventBus.fire(GroupUriChangedEvent(oldGroupUri, newGroupUri)); _reparentGroupPaths(groupChildrenUris, newGroupUri); } } @@ -248,3 +262,11 @@ class FilterGrouping with ChangeNotifier { }).whereNotNullKey(); } } + +@immutable +class GroupUriChangedEvent { + final Uri oldGroupUri; + final Uri newGroupUri; + + const GroupUriChangedEvent(this.oldGroupUri, this.newGroupUri); +} diff --git a/lib/model/grouping/convert.dart b/lib/model/grouping/convert.dart index 01e94d05b..2051d2baa 100644 --- a/lib/model/grouping/convert.dart +++ b/lib/model/grouping/convert.dart @@ -1,6 +1,6 @@ import 'package:aves/model/dynamic_albums.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; -import 'package:aves/model/filters/covered/group_base.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; +import 'package:aves/model/filters/container/group_base.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/grouping/common.dart'; diff --git a/lib/model/settings/modules/filter_grids.dart b/lib/model/settings/modules/filter_grids.dart index 05a443b57..4af197ded 100644 --- a/lib/model/settings/modules/filter_grids.dart +++ b/lib/model/settings/modules/filter_grids.dart @@ -1,6 +1,10 @@ +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/settings/defaults.dart'; +import 'package:aves/utils/collection_utils.dart'; import 'package:aves_model/aves_model.dart'; +import 'package:synchronized/synchronized.dart'; mixin FilterGridsSettings on SettingsAccess { AlbumChipSectionFactor get albumSectionFactor => getEnumOrDefault(SettingKeys.albumGroupFactorKey, SettingsDefaults.albumGroupFactor, AlbumChipSectionFactor.values); @@ -56,4 +60,41 @@ mixin FilterGridsSettings on SettingsAccess { void setShowTitleQuery(String routeName, bool newValue) => set(SettingKeys.showTitleQueryPrefixKey + routeName, newValue); // TODO TLAD [nested] save/load + + final _lockForPins = Lock(); + + Future updatePinnedDynamicAlbums(Map changes) async { + await _lockForPins.synchronized(() async { + final _pinnedFilters = pinnedFilters; + bool changed = false; + changes.forEach((oldFilter, newFilter) { + if (newFilter != null) { + changed |= _pinnedFilters.replace(oldFilter, newFilter); + } else { + changed |= _pinnedFilters.remove(oldFilter); + } + }); + if (changed) { + pinnedFilters = _pinnedFilters; + } + }); + } + + Future updatePinnedGroup(Uri oldGroupUri, Uri newGroupUri) async { + await _lockForPins.synchronized(() async { + final _pinnedFilters = pinnedFilters; + bool changed = false; + final grouping = FilterGrouping.forUri(oldGroupUri); + if (grouping != null) { + final oldFilter = grouping.uriToFilter(oldGroupUri); + final newFilter = grouping.uriToFilter(newGroupUri); + if (oldFilter != null && newFilter != null) { + changed |= _pinnedFilters.replace(oldFilter, newFilter); + } + } + if (changed) { + pinnedFilters = _pinnedFilters; + } + }); + } } diff --git a/lib/model/settings/modules/navigation.dart b/lib/model/settings/modules/navigation.dart index 95400360b..aa61440f2 100644 --- a/lib/model/settings/modules/navigation.dart +++ b/lib/model/settings/modules/navigation.dart @@ -1,7 +1,11 @@ -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/settings/defaults.dart'; +import 'package:aves/utils/collection_utils.dart'; import 'package:aves_model/aves_model.dart'; +import 'package:synchronized/synchronized.dart'; mixin NavigationSettings on SettingsAccess { bool get mustBackTwiceToExit => getBool(SettingKeys.mustBackTwiceToExitKey) ?? SettingsDefaults.mustBackTwiceToExit; @@ -72,4 +76,45 @@ mixin NavigationSettings on SettingsAccess { List get drawerPageBookmarks => getStringList(SettingKeys.drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks; set drawerPageBookmarks(List newValue) => set(SettingKeys.drawerPageBookmarksKey, newValue); + + final _lockForBookmarks = Lock(); + + Future updateBookmarkedDynamicAlbums(Map changes) async { + await _lockForBookmarks.synchronized(() async { + final _bookmarks = drawerAlbumBookmarks; + bool changed = false; + if (_bookmarks != null) { + changes.forEach((oldFilter, newFilter) { + if (newFilter != null) { + changed |= _bookmarks.replace(oldFilter, newFilter); + } else { + changed |= _bookmarks.remove(oldFilter); + } + }); + } + if (changed) { + drawerAlbumBookmarks = _bookmarks; + } + }); + } + + Future updateBookmarkedGroup(Uri oldGroupUri, Uri newGroupUri) async { + await _lockForBookmarks.synchronized(() async { + final _bookmarks = drawerAlbumBookmarks; + bool changed = false; + if (_bookmarks != null) { + final grouping = FilterGrouping.forUri(oldGroupUri); + if (grouping != null) { + final oldFilter = grouping.uriToFilter(oldGroupUri); + final newFilter = grouping.uriToFilter(newGroupUri); + if (oldFilter is AlbumBaseFilter && newFilter is AlbumBaseFilter) { + changed |= _bookmarks.replace(oldFilter, newFilter); + } + } + } + if (changed) { + drawerAlbumBookmarks = _bookmarks; + } + }); + } } diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 434896efb..80e36acf4 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -4,8 +4,10 @@ import 'dart:math'; import 'package:aves/app_flavor.dart'; import 'package:aves/model/device.dart'; +import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/map_style.dart'; @@ -67,10 +69,21 @@ class Settings with ChangeNotifier, SettingsAccess, SearchSettings, AppSettings, Future init({required bool monitorPlatformSettings}) async { await store.init(); resetAppliedLocale(); + _subscriptions + ..forEach((sub) => sub.cancel()) + ..clear(); + _subscriptions.add(dynamicAlbums.eventBus.on().listen((e) { + final changes = e.changes; + updateBookmarkedDynamicAlbums(changes); + updatePinnedDynamicAlbums(changes); + })); + _subscriptions.add(albumGrouping.eventBus.on().listen((e) { + final oldGroupUri = e.oldGroupUri; + final newGroupUri = e.newGroupUri; + updateBookmarkedGroup(oldGroupUri, newGroupUri); + updatePinnedGroup(oldGroupUri, newGroupUri); + })); if (monitorPlatformSettings) { - _subscriptions - ..forEach((sub) => sub.cancel()) - ..clear(); _subscriptions.add(_platformSettingsChangeChannel.receiveBroadcastStream().listen((event) => _onPlatformSettingsChanged(event as Map?))); } initAppSettings(); diff --git a/lib/model/source/album.dart b/lib/model/source/album.dart index 70670e87e..65602d441 100644 --- a/lib/model/source/album.dart +++ b/lib/model/source/album.dart @@ -1,6 +1,6 @@ import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index 9e48c9e90..dcbb82b51 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -8,7 +8,7 @@ import 'package:aves/model/entry/extensions/keys.dart'; import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/entry/sort.dart'; import 'package:aves/model/favourites.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 2bcd767bb..6ed144edf 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -3,10 +3,10 @@ import 'dart:math'; import 'package:aves/app_mode.dart'; import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/query.dart'; -import 'package:aves/model/filters/set_and.dart'; +import 'package:aves/model/filters/container/set_and.dart'; import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 0afc9433f..407f61315 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -9,9 +9,9 @@ import 'package:aves/model/entry/extensions/metadata_edition.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/favourites.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/set_and.dart'; +import 'package:aves/model/filters/container/set_and.dart'; import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/metadata/date_modifier.dart'; diff --git a/lib/widgets/debug/database.dart b/lib/widgets/debug/database.dart index a1120c8fa..42bdb6098 100644 --- a/lib/widgets/debug/database.dart +++ b/lib/widgets/debug/database.dart @@ -238,9 +238,17 @@ class _DebugAppDatabaseSectionState extends State with return Row( children: [ Expanded( - child: Text('cover rows: ${snapshot.data!.length} (${covers.count} in memory)'), + child: Text('covers: ${snapshot.data!.length} rows\n(${covers.count} in memory)'), ), const SizedBox(width: 8), + ElevatedButton( + onPressed: () => localMediaDb.loadAllCovers().then((list) { + debugPrint('covers dump start'); + list.forEach((v) => debugPrint(' $v')); + debugPrint('covers albums dump end'); + }), + child: const Text('Dump'), + ), ElevatedButton( onPressed: () => covers.clear().then((_) => _reload()), child: const Text('Clear'), @@ -259,9 +267,17 @@ class _DebugAppDatabaseSectionState extends State with return Row( children: [ Expanded( - child: Text('dynamic album rows: ${snapshot.data!.length} (${dynamicAlbums.count} in memory)'), + child: Text('dynamic albums: ${snapshot.data!.length} rows\n(${dynamicAlbums.count} in memory)'), ), const SizedBox(width: 8), + ElevatedButton( + onPressed: () => localMediaDb.loadAllDynamicAlbums().then((list) { + debugPrint('dynamic albums dump start'); + list.forEach((v) => debugPrint(' $v')); + debugPrint('dynamic albums dump end'); + }), + child: const Text('Dump'), + ), ElevatedButton( onPressed: () => dynamicAlbums.clear().then((_) => _reload()), child: const Text('Clear'), diff --git a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart index 2fc110f80..3a930bf3e 100644 --- a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart @@ -1,6 +1,6 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/grouping/common.dart'; diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index f6b3c66d2..d85b297d7 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -2,8 +2,8 @@ import 'package:aves/model/app_inventory.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/entry/extensions/props.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/grouping/common.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index 71983fb3b..1654d7fd8 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -5,8 +5,8 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/grouping/common.dart'; @@ -21,7 +21,6 @@ import 'package:aves/services/common/services.dart'; import 'package:aves/services/media/enums.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/utils/android_file_utils.dart'; -import 'package:aves/utils/collection_utils.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/action_mixins/entry_storage.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; @@ -41,7 +40,6 @@ import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; @@ -526,68 +524,19 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate } } - Future _doRenameNonStoredAlbum( - BuildContext context, - T oldFilter, - Future Function() createNewFilter, - bool Function(T filter) isRenamed, - ) async { - // save cover and bookmark before renaming - final cover = await covers.remove(oldFilter, notify: false); - final bookmarks = settings.drawerAlbumBookmarks; - final pinnedFilters = settings.pinnedFilters; - - // new filter to match new name - final newFilter = await createNewFilter(); + Future _doRenameAlbumGroup(BuildContext context, AlbumGroupFilter oldFilter, Uri newUri) async { + albumGrouping.rename(oldFilter.uri, newUri); + final newFilter = albumGrouping.uriToFilter(newUri); if (newFilter == null) { showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); return; } - bool _isRenamed(CollectionFilter? v) => v is T && isRenamed(v); - - // update cover - if (cover != null) { - await covers.set( - filter: newFilter, - entryId: cover.$1, - packageName: cover.$2, - color: cover.$3, - notify: true, - ); - } - - // update drawer bookmark - final bookmark = bookmarks?.firstWhereOrNull(_isRenamed); - if (bookmark != null) { - bookmarks?.replace(bookmark, newFilter); - settings.drawerAlbumBookmarks = bookmarks; - } - - // update pin - final pin = pinnedFilters.firstWhereOrNull(_isRenamed); - if (pin != null) { - pinnedFilters.replace(pin, newFilter); - settings.pinnedFilters = pinnedFilters; - } - browse(context); } - Future _doRenameAlbumGroup(BuildContext context, AlbumGroupFilter oldFilter, Uri newUri) async { - final oldUri = oldFilter.uri; - await _doRenameNonStoredAlbum(context, oldFilter, () { - albumGrouping.rename(oldUri, newUri); - final newFilter = albumGrouping.uriToFilter(newUri); - return SynchronousFuture(newFilter is AlbumGroupFilter ? newFilter : null); - }, (filter) => filter.uri == oldUri); - } - Future _doRenameDynamicAlbum(BuildContext context, DynamicAlbumFilter oldFilter, String newName) async { - final oldName = oldFilter.name; - await _doRenameNonStoredAlbum(context, oldFilter, () async { - await dynamicAlbums.rename(oldFilter, newName); - return DynamicAlbumFilter(newName, oldFilter.filter); - }, (filter) => filter.name == oldName); + await dynamicAlbums.rename(oldFilter, newName); + browse(context); } Future _doRenameStoredAlbum(BuildContext context, StoredAlbumFilter albumFilter, String newName) async { diff --git a/lib/widgets/filter_grids/common/action_delegates/chip.dart b/lib/widgets/filter_grids/common/action_delegates/chip.dart index c04d4f80d..edb053fea 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index d71011946..da60632f5 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -3,7 +3,7 @@ import 'package:aves/model/covers.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/filters/container/set_or.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/filter_grids/common/covered_filter_chip.dart b/lib/widgets/filter_grids/common/covered_filter_chip.dart index fe9a6132f..83058c16d 100644 --- a/lib/widgets/filter_grids/common/covered_filter_chip.dart +++ b/lib/widgets/filter_grids/common/covered_filter_chip.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:aves/model/app_inventory.dart'; import 'package:aves/model/covers.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index bf0da1499..5f16c71ad 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -311,7 +311,7 @@ class _FilterGridContentState extends State<_FilterG visibleSections = {}; final queryUp = query.toUpperCase(); widget.sections.forEach((sectionKey, sectionFilters) { - final visibleFilters = sectionFilters.where((item) => item.filter.match(context, queryUp)).toList(); + final visibleFilters = sectionFilters.where((item) => item.filter.matchLabel(context, queryUp)).toList(); if (visibleFilters.isNotEmpty) { visibleSections[sectionKey] = visibleFilters; } diff --git a/lib/widgets/filter_grids/common/filter_nav_page.dart b/lib/widgets/filter_grids/common/filter_nav_page.dart index 8784a1384..35f789c64 100644 --- a/lib/widgets/filter_grids/common/filter_nav_page.dart +++ b/lib/widgets/filter_grids/common/filter_nav_page.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/selection.dart'; diff --git a/lib/widgets/filter_grids/common/list_details.dart b/lib/widgets/filter_grids/common/list_details.dart index 825fc332a..79004d4a1 100644 --- a/lib/widgets/filter_grids/common/list_details.dart +++ b/lib/widgets/filter_grids/common/list_details.dart @@ -1,6 +1,6 @@ import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_source.dart'; diff --git a/lib/widgets/navigation/drawer/app_drawer.dart b/lib/widgets/navigation/drawer/app_drawer.dart index 9740aaf77..beac79147 100644 --- a/lib/widgets/navigation/drawer/app_drawer.dart +++ b/lib/widgets/navigation/drawer/app_drawer.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/navigation/drawer/collection_nav_tile.dart b/lib/widgets/navigation/drawer/collection_nav_tile.dart index 6613d4f6a..abc03b58f 100644 --- a/lib/widgets/navigation/drawer/collection_nav_tile.dart +++ b/lib/widgets/navigation/drawer/collection_nav_tile.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_source.dart'; diff --git a/lib/widgets/navigation/tv_rail.dart b/lib/widgets/navigation/tv_rail.dart index 5d72bdc2e..35f58ace3 100644 --- a/lib/widgets/navigation/tv_rail.dart +++ b/lib/widgets/navigation/tv_rail.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/enums/home_page.dart'; diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index 3b8fc2527..20aeee4b3 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -1,7 +1,7 @@ import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/filters/aspect_ratio.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; @@ -13,7 +13,7 @@ import 'package:aves/model/filters/missing.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/recent.dart'; -import 'package:aves/model/filters/set_and.dart'; +import 'package:aves/model/filters/container/set_and.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/model/filters/weekday.dart'; import 'package:aves/model/grouping/common.dart'; diff --git a/lib/widgets/settings/navigation/drawer.dart b/lib/widgets/settings/navigation/drawer.dart index 5cc6fc8d2..6689e5deb 100644 --- a/lib/widgets/settings/navigation/drawer.dart +++ b/lib/widgets/settings/navigation/drawer.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/recent.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/settings/navigation/drawer_tab_albums.dart b/lib/widgets/settings/navigation/drawer_tab_albums.dart index c48e6c222..84b2051ab 100644 --- a/lib/widgets/settings/navigation/drawer_tab_albums.dart +++ b/lib/widgets/settings/navigation/drawer_tab_albums.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/filters/covered/album_group.dart'; +import 'package:aves/model/filters/container/album_group.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/pubspec.lock b/pubspec.lock index ba6cf30f3..7cc2091eb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1508,7 +1508,7 @@ packages: source: hosted version: "0.3.1" synchronized: - dependency: transitive + dependency: "direct main" description: name: synchronized sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" diff --git a/pubspec.yaml b/pubspec.yaml index d546ca603..676395d38 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -124,6 +124,7 @@ dependencies: streams_channel: git: url: https://github.com/deckerst/aves_streams_channel.git + synchronized: url_launcher: vector_map_tiles: ^8.0.0 # vector_map_tiles v9.0.0-beta.6 has a buggy cross-platform definition for `cacheFolder` vector_math: diff --git a/test/model/filters_test.dart b/test/model/filters_test.dart index c34dc5d88..25743810b 100644 --- a/test/model/filters_test.dart +++ b/test/model/filters_test.dart @@ -1,7 +1,7 @@ import 'package:aves/model/filters/aspect_ratio.dart'; import 'package:aves/model/filters/coordinate.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; import 'package:aves/model/filters/covered/tag.dart'; @@ -15,8 +15,8 @@ import 'package:aves/model/filters/placeholder.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/recent.dart'; -import 'package:aves/model/filters/set_and.dart'; -import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/filters/container/set_and.dart'; +import 'package:aves/model/filters/container/set_or.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/model/filters/weekday.dart'; import 'package:aves/model/grouping/common.dart'; diff --git a/test/model/grouping/common_test.dart b/test/model/grouping/common_test.dart index 3527804f6..e1565981d 100644 --- a/test/model/grouping/common_test.dart +++ b/test/model/grouping/common_test.dart @@ -1,9 +1,9 @@ import 'package:aves/model/db/db.dart'; import 'package:aves/model/dynamic_albums.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; -import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/filters/container/set_or.dart'; import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/grouping/convert.dart'; import 'package:aves/services/common/services.dart'; diff --git a/test/model/grouping/convert_test.dart b/test/model/grouping/convert_test.dart index e88fd8c96..7acf2f5e2 100644 --- a/test/model/grouping/convert_test.dart +++ b/test/model/grouping/convert_test.dart @@ -1,9 +1,9 @@ import 'package:aves/model/db/db.dart'; import 'package:aves/model/dynamic_albums.dart'; -import 'package:aves/model/filters/covered/album_group.dart'; -import 'package:aves/model/filters/covered/dynamic_album.dart'; +import 'package:aves/model/filters/container/album_group.dart'; +import 'package:aves/model/filters/container/dynamic_album.dart'; import 'package:aves/model/filters/covered/stored_album.dart'; -import 'package:aves/model/filters/set_or.dart'; +import 'package:aves/model/filters/container/set_or.dart'; import 'package:aves/model/grouping/common.dart'; import 'package:aves/model/grouping/convert.dart'; import 'package:aves/services/common/services.dart';