diff --git a/CHANGELOG.md b/CHANGELOG.md
index c67508ef8..d9fb05c21 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+### Added
+
+- Albums: dynamic albums from filter sets
+
## [v1.11.19] - 2024-11-24
### Added
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 781a00af8..a04876369 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -85,6 +85,7 @@
"sourceStateLocatingPlaces": "Locating places",
"chipActionDelete": "Delete",
+ "chipActionRemove": "Remove",
"chipActionShowCollection": "Show in Collection",
"chipActionGoToAlbumPage": "Show in Albums",
"chipActionGoToCountryPage": "Show in Countries",
@@ -204,6 +205,7 @@
"albumTierSpecial": "Common",
"albumTierApps": "Apps",
"albumTierVaults": "Vaults",
+ "albumTierDynamic": "Dynamic",
"albumTierRegular": "Others",
"coordinateFormatDms": "DMS",
@@ -427,6 +429,9 @@
"newAlbumDialogNameLabelAlreadyExistsHelper": "Directory already exists",
"newAlbumDialogStorageLabel": "Storage:",
+ "newDynamicAlbumDialogTitle": "New Dynamic Album",
+ "dynamicAlbumAlreadyExists": "Dynamic album already exists",
+
"newVaultWarningDialogMessage": "Items in vaults are only available to this app and no others.\n\nIf you uninstall this app, or clear this app data, you will lose all these items.",
"newVaultDialogTitle": "New Vault",
"configureVaultDialogTitle": "Configure Vault",
@@ -595,6 +600,7 @@
"collectionActionShowTitleSearch": "Show title filter",
"collectionActionHideTitleSearch": "Hide title filter",
+ "collectionActionAddDynamicAlbum": "Add dynamic album",
"collectionActionAddShortcut": "Add shortcut",
"collectionActionSetHome": "Set as home",
"collectionActionEmptyBin": "Empty bin",
@@ -806,6 +812,7 @@
"settingsActionImportDialogTitle": "Import",
"appExportCovers": "Covers",
+ "appExportDynamicAlbums": "Dynamic albums",
"appExportFavourites": "Favorites",
"appExportSettings": "Settings",
diff --git a/lib/model/covers.dart b/lib/model/covers.dart
index 231d8effc..edf189280 100644
--- a/lib/model/covers.dart
+++ b/lib/model/covers.dart
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/entry/entry.dart';
-import 'package:aves/model/filters/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';
import 'package:aves/model/vaults/vaults.dart';
@@ -16,6 +16,8 @@ import 'package:flutter/painting.dart';
final Covers covers = Covers._private();
+typedef CoverProps = (int? entryId, String? packageName, Color? color);
+
class Covers {
final StreamController?> _entryChangeStreamController = StreamController.broadcast();
final StreamController?> _packageChangeStreamController = StreamController.broadcast();
@@ -39,22 +41,60 @@ class Covers {
Set get all => Set.unmodifiable(_rows);
- (int? entryId, String? packageName, Color? color)? of(CollectionFilter filter) {
- if (filter is AlbumFilter && vaults.isLocked(filter.album)) return null;
+ CoverProps? of(CollectionFilter filter) {
+ if (filter is StoredAlbumFilter && vaults.isLocked(filter.album)) return null;
final row = _rows.firstWhereOrNull((row) => row.filter == filter);
return row != null ? (row.entryId, row.packageName, row.color) : null;
}
+ Future remove(CollectionFilter filter, {bool notify = true}) async {
+ final props = of(filter);
+ if (props != null) {
+ await set(filter: filter, entryId: null, packageName: null, color: null);
+
+ if (notify) {
+ final (entryId, packageName, color) = props;
+ if (entryId != null) _entryChangeStreamController.add({filter});
+ if (packageName != null) _packageChangeStreamController.add({filter});
+ if (color != null) _colorChangeStreamController.add({filter});
+ }
+ }
+ return props;
+ }
+
+ Future removeAll(Set filters, {bool notify = true}) async {
+ final entryIdChanged = {};
+ final packageNameChanged = {};
+ final colorChanged = {};
+
+ for (final filter in filters) {
+ final props = await remove(filter, notify: false);
+ if (notify && props != null) {
+ final (entryId, packageName, color) = props;
+ if (entryId != null) entryIdChanged.add(filter);
+ if (packageName != null) packageNameChanged.add(filter);
+ if (color != null) colorChanged.add(filter);
+ }
+ }
+
+ if (notify) {
+ if (entryIdChanged.isNotEmpty) _entryChangeStreamController.add(entryIdChanged);
+ if (packageNameChanged.isNotEmpty) _packageChangeStreamController.add(packageNameChanged);
+ if (colorChanged.isNotEmpty) _colorChangeStreamController.add(colorChanged);
+ }
+ }
+
Future set({
required CollectionFilter filter,
required int? entryId,
required String? packageName,
required Color? color,
+ bool notify = true,
}) async {
// erase contextual properties from filters before saving them
- if (filter is AlbumFilter) {
- filter = AlbumFilter(filter.album, null);
+ if (filter is StoredAlbumFilter) {
+ filter = StoredAlbumFilter(filter.album, null);
}
final oldRows = _rows.where((row) => row.filter == filter).toSet();
@@ -77,9 +117,11 @@ class Covers {
await localMediaDb.addCovers({row});
}
- if (oldEntry != entryId) _entryChangeStreamController.add({filter});
- if (oldPackage != packageName) _packageChangeStreamController.add({filter});
- if (oldColor != color) _colorChangeStreamController.add({filter});
+ if (notify) {
+ if (oldEntry != entryId) _entryChangeStreamController.add({filter});
+ if (oldPackage != packageName) _packageChangeStreamController.add({filter});
+ if (oldColor != color) _colorChangeStreamController.add({filter});
+ }
}
Future _removeEntryFromRows(Set rows) {
@@ -112,7 +154,7 @@ class Covers {
}
AlbumType effectiveAlbumType(String albumPath) {
- final filterPackage = of(AlbumFilter(albumPath, null))?.$2;
+ final filterPackage = of(StoredAlbumFilter(albumPath, null))?.$2;
if (filterPackage != null) {
return filterPackage.isEmpty ? AlbumType.regular : AlbumType.app;
} else {
@@ -121,7 +163,7 @@ class Covers {
}
String? effectiveAlbumPackage(String albumPath) {
- final filterPackage = of(AlbumFilter(albumPath, null))?.$2;
+ final filterPackage = of(StoredAlbumFilter(albumPath, null))?.$2;
return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath);
}
@@ -129,7 +171,7 @@ class Covers {
List