#268 cover/pins/bookmarks sub to dynamics/groups; dynamics sub to groups;
container filter mixin; debug: cover/dynamics dump;
This commit is contained in:
parent
27879a900d
commit
651b5926dc
57 changed files with 478 additions and 189 deletions
|
@ -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',
|
||||
|
|
|
@ -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<StreamSubscription> _subscriptions = [];
|
||||
final _lock = Lock();
|
||||
|
||||
final StreamController<Set<CollectionFilter>?> _entryChangeStreamController = StreamController.broadcast();
|
||||
final StreamController<Set<CollectionFilter>?> _packageChangeStreamController = StreamController.broadcast();
|
||||
final StreamController<Set<CollectionFilter>?> _colorChangeStreamController = StreamController.broadcast();
|
||||
|
@ -37,6 +44,8 @@ class Covers {
|
|||
|
||||
Future<void> init() async {
|
||||
_rows = await localMediaDb.loadAllCovers();
|
||||
_subscriptions.add(dynamicAlbums.eventBus.on<DynamicAlbumChangedEvent>().listen((e) => _updateCoveredDynamicAlbums(e.changes)));
|
||||
_subscriptions.add(albumGrouping.eventBus.on<GroupUriChangedEvent>().listen((e) => _updateCoveredGroup(e.oldGroupUri, e.newGroupUri)));
|
||||
}
|
||||
|
||||
int get count => _rows.length;
|
||||
|
@ -172,6 +181,49 @@ class Covers {
|
|||
return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath);
|
||||
}
|
||||
|
||||
Future<void> _updateCoveredDynamicAlbums(Map<DynamicAlbumFilter, DynamicAlbumFilter?> 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<void> _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<Map<String, dynamic>>? export(CollectionSource source) {
|
||||
|
|
|
@ -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<StreamSubscription> _subscriptions = [];
|
||||
final _lock = Lock();
|
||||
Set<DynamicAlbumFilter> _rows = {};
|
||||
|
||||
final EventBus eventBus = EventBus();
|
||||
|
||||
DynamicAlbums._private() {
|
||||
if (kFlutterMemoryAllocationsEnabled) ChangeNotifier.maybeDispatchObjectCreation(this);
|
||||
_subscriptions.add(albumGrouping.eventBus.on<GroupUriChangedEvent>().listen((e) => _onGroupUriChanged(e.oldGroupUri, e.newGroupUri)));
|
||||
}
|
||||
|
||||
Future<void> init() async {
|
||||
|
@ -22,38 +34,86 @@ class DynamicAlbums with ChangeNotifier {
|
|||
|
||||
Set<DynamicAlbumFilter> get all => Set.unmodifiable(_rows);
|
||||
|
||||
Future<void> add(DynamicAlbumFilter filter) async {
|
||||
await localMediaDb.addDynamicAlbums({DynamicAlbumRow(name: filter.name, filter: filter.filter)});
|
||||
_rows.add(filter);
|
||||
Future<void> _doAdd(Set<DynamicAlbumFilter>? 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);
|
||||
}
|
||||
|
||||
Future<void> _doRemove(Set<String>? names) async {
|
||||
if (names == null || names.isEmpty) return;
|
||||
await localMediaDb.removeDynamicAlbums(names);
|
||||
_rows.removeWhere((v) => names.contains(v.name));
|
||||
}
|
||||
|
||||
Future<void> add(DynamicAlbumFilter filter) async {
|
||||
await _lock.synchronized(() async {
|
||||
await _doAdd({filter});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> remove(Set<DynamicAlbumFilter> 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();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> 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<void> update(Map<DynamicAlbumFilter, DynamicAlbumFilter?> 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<void> clear() async {
|
||||
await _lock.synchronized(() async {
|
||||
await localMediaDb.clearDynamicAlbums();
|
||||
_rows.clear();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> rename(DynamicAlbumFilter filter, String newName) async {
|
||||
await localMediaDb.removeDynamicAlbums({filter.name});
|
||||
_rows.remove(filter);
|
||||
|
||||
await add(DynamicAlbumFilter(newName, filter.filter));
|
||||
});
|
||||
}
|
||||
|
||||
DynamicAlbumFilter? get(String name) => _rows.firstWhereOrNull((row) => row.name == name);
|
||||
|
||||
bool contains(String? name) => name != null && get(name) != null;
|
||||
|
||||
Future<void> _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 = <DynamicAlbumFilter, DynamicAlbumFilter?>{};
|
||||
oldDynamicAlbumFilters.forEach((oldFilter) {
|
||||
final newFilter = oldFilter.replaceFilters(transformer);
|
||||
newFilters[oldFilter] = newFilter;
|
||||
});
|
||||
await update(newFilters);
|
||||
}
|
||||
|
||||
// import/export
|
||||
|
||||
List<String>? export() {
|
||||
|
@ -107,3 +167,10 @@ class DynamicAlbumRow extends Equatable {
|
|||
'filter': filter.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@immutable
|
||||
class DynamicAlbumChangedEvent {
|
||||
final Map<DynamicAlbumFilter, DynamicAlbumFilter?> changes;
|
||||
|
||||
const DynamicAlbumChangedEvent(this.changes);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
11
lib/model/filters/container/container.dart
Normal file
11
lib/model/filters/container/container.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
|
||||
mixin ContainerFilter<T extends CollectionFilter> on CollectionFilter {
|
||||
Set<CollectionFilter> get innerFilters;
|
||||
|
||||
bool containsFilter(CollectionFilterPredicate test) {
|
||||
return innerFilters.any(test) || innerFilters.whereType<ContainerFilter>().any((v) => v.containsFilter(test));
|
||||
}
|
||||
|
||||
T? replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement);
|
||||
}
|
|
@ -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<CollectionFilter> get innerFilters => {filter};
|
||||
|
||||
@override
|
||||
DynamicAlbumFilter? replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement) {
|
||||
final newFilter = toElement(filter);
|
||||
return newFilter != null
|
||||
? DynamicAlbumFilter(
|
||||
name,
|
||||
newFilter,
|
||||
reversed: reversed,
|
||||
)
|
||||
: null;
|
||||
}
|
||||
}
|
|
@ -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<CollectionFilter> get innerFilters => {filter};
|
||||
}
|
|
@ -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<CollectionFilter> _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<CollectionFilter> get innerFilters => _filters.toSet();
|
||||
|
||||
SetAndFilter(Set<CollectionFilter> 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<CollectionFilter> get innerFilters => _filters.toSet();
|
||||
|
||||
@override
|
||||
SetAndFilter replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement) {
|
||||
return SetAndFilter(
|
||||
_filters.map((v) => toElement(v)).nonNulls.toSet(),
|
||||
reversed: reversed,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<CollectionFilter> _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<CollectionFilter> get innerFilters => _filters.toSet();
|
||||
|
||||
SetOrFilter(Set<CollectionFilter> 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<CollectionFilter> get innerFilters => _filters.toSet();
|
||||
|
||||
@override
|
||||
SetOrFilter replaceFilters(CollectionFilter? Function(CollectionFilter oldFilter) toElement) {
|
||||
return SetOrFilter(
|
||||
_filters.map((v) => toElement(v)).nonNulls.toSet(),
|
||||
reversed: reversed,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<Object?> 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)}';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Object?> 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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -33,7 +33,7 @@ class FavouriteFilter extends CollectionFilter {
|
|||
};
|
||||
|
||||
@override
|
||||
EntryFilter get positiveTest => _test;
|
||||
EntryPredicate get positiveTest => _test;
|
||||
|
||||
@override
|
||||
bool get exclusiveProp => false;
|
||||
|
|
|
@ -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<Collecti
|
|||
|
||||
String toJson() => 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<Collecti
|
|||
|
||||
String getTooltip(BuildContext context) => 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<T extends CollectionFilter> 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<Object?> get props => throw UnimplementedError();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Object?> 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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Object?> 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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -29,7 +29,7 @@ class TrashFilter extends CollectionFilter {
|
|||
};
|
||||
|
||||
@override
|
||||
EntryFilter get positiveTest => _test;
|
||||
EntryPredicate get positiveTest => _test;
|
||||
|
||||
@override
|
||||
bool get exclusiveProp => false;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Object?> 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;
|
||||
|
|
|
@ -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<T extends GroupBaseFilter> 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<Uri, Set<Uri>> _groups = {};
|
||||
|
@ -30,6 +33,15 @@ class FilterGrouping<T extends GroupBaseFilter> 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<T extends GroupBaseFilter> 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<T extends GroupBaseFilter> with ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
eventBus.fire(GroupUriChangedEvent(oldGroupUri, newGroupUri));
|
||||
_reparentGroupPaths(groupChildrenUris, newGroupUri);
|
||||
}
|
||||
}
|
||||
|
@ -248,3 +262,11 @@ class FilterGrouping<T extends GroupBaseFilter> with ChangeNotifier {
|
|||
}).whereNotNullKey();
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GroupUriChangedEvent {
|
||||
final Uri oldGroupUri;
|
||||
final Uri newGroupUri;
|
||||
|
||||
const GroupUriChangedEvent(this.oldGroupUri, this.newGroupUri);
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<void> updatePinnedDynamicAlbums(Map<DynamicAlbumFilter, DynamicAlbumFilter?> 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<void> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> get drawerPageBookmarks => getStringList(SettingKeys.drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks;
|
||||
|
||||
set drawerPageBookmarks(List<String> newValue) => set(SettingKeys.drawerPageBookmarksKey, newValue);
|
||||
|
||||
final _lockForBookmarks = Lock();
|
||||
|
||||
Future<void> updateBookmarkedDynamicAlbums(Map<DynamicAlbumFilter, DynamicAlbumFilter?> 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<void> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<void> init({required bool monitorPlatformSettings}) async {
|
||||
await store.init();
|
||||
resetAppliedLocale();
|
||||
if (monitorPlatformSettings) {
|
||||
_subscriptions
|
||||
..forEach((sub) => sub.cancel())
|
||||
..clear();
|
||||
_subscriptions.add(dynamicAlbums.eventBus.on<DynamicAlbumChangedEvent>().listen((e) {
|
||||
final changes = e.changes;
|
||||
updateBookmarkedDynamicAlbums(changes);
|
||||
updatePinnedDynamicAlbums(changes);
|
||||
}));
|
||||
_subscriptions.add(albumGrouping.eventBus.on<GroupUriChangedEvent>().listen((e) {
|
||||
final oldGroupUri = e.oldGroupUri;
|
||||
final newGroupUri = e.newGroupUri;
|
||||
updateBookmarkedGroup(oldGroupUri, newGroupUri);
|
||||
updatePinnedGroup(oldGroupUri, newGroupUri);
|
||||
}));
|
||||
if (monitorPlatformSettings) {
|
||||
_subscriptions.add(_platformSettingsChangeChannel.receiveBroadcastStream().listen((event) => _onPlatformSettingsChanged(event as Map?)));
|
||||
}
|
||||
initAppSettings();
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -238,9 +238,17 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> 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<DebugAppDatabaseSection> 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'),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<AlbumBaseFilter>
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _doRenameNonStoredAlbum<T extends AlbumBaseFilter>(
|
||||
BuildContext context,
|
||||
T oldFilter,
|
||||
Future<T?> 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<void> _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<void> _doRenameAlbumGroup(BuildContext context, AlbumGroupFilter oldFilter, Uri newUri) async {
|
||||
final oldUri = oldFilter.uri;
|
||||
await _doRenameNonStoredAlbum<AlbumGroupFilter>(context, oldFilter, () {
|
||||
albumGrouping.rename(oldUri, newUri);
|
||||
final newFilter = albumGrouping.uriToFilter(newUri);
|
||||
return SynchronousFuture(newFilter is AlbumGroupFilter ? newFilter : null);
|
||||
}, (filter) => filter.uri == oldUri);
|
||||
}
|
||||
|
||||
Future<void> _doRenameDynamicAlbum(BuildContext context, DynamicAlbumFilter oldFilter, String newName) async {
|
||||
final oldName = oldFilter.name;
|
||||
await _doRenameNonStoredAlbum<DynamicAlbumFilter>(context, oldFilter, () async {
|
||||
await dynamicAlbums.rename(oldFilter, newName);
|
||||
return DynamicAlbumFilter(newName, oldFilter.filter);
|
||||
}, (filter) => filter.name == oldName);
|
||||
browse(context);
|
||||
}
|
||||
|
||||
Future<void> _doRenameStoredAlbum(BuildContext context, StoredAlbumFilter albumFilter, String newName) async {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -311,7 +311,7 @@ class _FilterGridContentState<T extends CollectionFilter> 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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -1508,7 +1508,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.3.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in a new issue