#1370 dynamic albums: decompose action

This commit is contained in:
Thibault Deckers 2025-01-01 19:11:27 +01:00
parent 43a42ad1dc
commit 9bdb3171d9
18 changed files with 123 additions and 79 deletions

View file

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased] ## <a id="unreleased"></a>[Unreleased]
### Added
- dynamic album decompose action
## <a id="v1.12.0"></a>[v1.12.0] - 2024-12-19 ## <a id="v1.12.0"></a>[v1.12.0] - 2024-12-19
### Added ### Added

View file

@ -92,6 +92,7 @@
"chipActionGoToPlacePage": "Show in Places", "chipActionGoToPlacePage": "Show in Places",
"chipActionGoToTagPage": "Show in Tags", "chipActionGoToTagPage": "Show in Tags",
"chipActionGoToExplorerPage": "Show in Explorer", "chipActionGoToExplorerPage": "Show in Explorer",
"chipActionDecompose": "Split",
"chipActionFilterOut": "Filter out", "chipActionFilterOut": "Filter out",
"chipActionFilterIn": "Filter in", "chipActionFilterIn": "Filter in",
"chipActionHide": "Hide", "chipActionHide": "Hide",

View file

@ -18,6 +18,8 @@ class SetAndFilter extends CollectionFilter {
CollectionFilter get _first => _filters.first; CollectionFilter get _first => _filters.first;
Set<CollectionFilter> get innerFilters => _filters.toSet();
SetAndFilter(Set<CollectionFilter> filters, {super.reversed = false}) { SetAndFilter(Set<CollectionFilter> filters, {super.reversed = false}) {
_filters = filters.toList().sorted(); _filters = filters.toList().sorted();
_test = (entry) => _filters.every((v) => v.test(entry)); _test = (entry) => _filters.every((v) => v.test(entry));

View file

@ -167,10 +167,12 @@ class CollectionLens with ChangeNotifier {
} }
} }
void addFilter(CollectionFilter filter) { void addFilters(Set<CollectionFilter> newFilters) {
if (filters.contains(filter)) return; if (filters.containsAll(newFilters)) return;
for (final filter in newFilters) {
filters.removeWhere((other) => !filter.isCompatible(other)); filters.removeWhere((other) => !filter.isCompatible(other));
filters.add(filter); }
filters.addAll(newFilters);
_onFilterChanged(); _onFilterChanged();
} }

View file

@ -145,6 +145,7 @@ class AIcons {
static final showFullscreenArrows = MdiIcons.arrowExpand; static final showFullscreenArrows = MdiIcons.arrowExpand;
static const showFullscreenCorners = Icons.fullscreen_outlined; static const showFullscreenCorners = Icons.fullscreen_outlined;
static const slideshow = Icons.slideshow_outlined; static const slideshow = Icons.slideshow_outlined;
static const split = Icons.call_split_outlined;
static const stats = Icons.donut_small_outlined; static const stats = Icons.donut_small_outlined;
static const vaultLock = Icons.lock_outlined; static const vaultLock = Icons.lock_outlined;
static const vaultAdd = Icons.enhanced_encryption_outlined; static const vaultAdd = Icons.enhanced_encryption_outlined;

View file

@ -16,6 +16,7 @@ extension ExtraChipActionView on ChipAction {
ChipAction.ratingOrLower => ChipAction.ratingOrLower =>
// different data depending on state // different data depending on state
toString(), toString(),
ChipAction.decompose => l10n.chipActionDecompose,
ChipAction.reverse => ChipAction.reverse =>
// different data depending on state // different data depending on state
l10n.chipActionFilterOut, l10n.chipActionFilterOut,
@ -33,6 +34,7 @@ extension ExtraChipActionView on ChipAction {
ChipAction.goToTagPage => AIcons.tag, ChipAction.goToTagPage => AIcons.tag,
ChipAction.goToExplorerPage => AIcons.explorer, ChipAction.goToExplorerPage => AIcons.explorer,
ChipAction.ratingOrGreater || ChipAction.ratingOrLower => AIcons.rating, ChipAction.ratingOrGreater || ChipAction.ratingOrLower => AIcons.rating,
ChipAction.decompose => AIcons.split,
ChipAction.reverse => AIcons.reverse, ChipAction.reverse => AIcons.reverse,
ChipAction.hide => AIcons.hide, ChipAction.hide => AIcons.hide,
ChipAction.lockVault => AIcons.vaultLock, ChipAction.lockVault => AIcons.vaultLock,

View file

@ -3,8 +3,10 @@ import 'dart:math';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/filters/covered/dynamic_album.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/set_and.dart';
import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/filters/trash.dart';
import 'package:aves/model/query.dart'; import 'package:aves/model/query.dart';
import 'package:aves/model/selection.dart'; import 'package:aves/model/selection.dart';
@ -33,8 +35,8 @@ import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/common/search/route.dart';
import 'package:aves/widgets/common/tile_extent_controller.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart';
import 'package:aves/widgets/dialogs/tile_view_dialog.dart'; import 'package:aves/widgets/dialogs/tile_view_dialog.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/search/search_delegate.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
@ -199,10 +201,22 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
), ),
), ),
if (showFilterBar) if (showFilterBar)
NotificationListener<FilterNotification>( NotificationListener(
onNotification: (notification) { onNotification: (notification) {
collection.addFilter(notification.filter); if (notification is SelectFilterNotification) {
collection.addFilters({notification.filter});
return true; return true;
} else if (notification is DecomposeFilterNotification) {
final filter = notification.filter;
if (filter is DynamicAlbumFilter) {
final innerFilter = filter.filter;
final newFilters = innerFilter is SetAndFilter ? innerFilter.innerFilters : {innerFilter};
collection.addFilters(newFilters);
collection.removeFilter(filter);
return true;
}
}
return false;
}, },
child: FilterBar( child: FilterBar(
filters: visibleFilters, filters: visibleFilters,

View file

@ -3,12 +3,8 @@ import 'dart:math';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/covers.dart'; import 'package:aves/model/covers.dart';
import 'package:aves/model/filters/covered/stored_album.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/covered/location.dart';
import 'package:aves/model/filters/path.dart';
import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/covered/tag.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
@ -101,21 +97,6 @@ class AvesFilterChip extends StatefulWidget {
static Future<void> showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async { static Future<void> showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async {
if (context.read<ValueNotifier<AppMode>>().value.canNavigate) { if (context.read<ValueNotifier<AppMode>>().value.canNavigate) {
final actions = <ChipAction>[
if (filter is AlbumBaseFilter) ChipAction.goToAlbumPage,
if (filter is StoredAlbumFilter || filter is PathFilter) ChipAction.goToExplorerPage,
if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage,
if ((filter is LocationFilter && filter.level == LocationLevel.place)) ChipAction.goToPlacePage,
if (filter is TagFilter) ChipAction.goToTagPage,
if (filter is RatingFilter && 1 < filter.rating && filter.rating < 5) ...[
if (filter.op != RatingFilter.opOrGreater) ChipAction.ratingOrGreater,
if (filter.op != RatingFilter.opOrLower) ChipAction.ratingOrLower,
],
ChipAction.reverse,
ChipAction.hide,
ChipAction.lockVault,
];
// remove focus, if any, to prevent the keyboard from showing up // remove focus, if any, to prevent the keyboard from showing up
// after the user is done with the popup menu // after the user is done with the popup menu
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
@ -132,7 +113,7 @@ class AvesFilterChip extends StatefulWidget {
child: Text(filter.getLabel(context)), child: Text(filter.getLabel(context)),
), ),
const PopupMenuDivider(), const PopupMenuDivider(),
...actions.where((action) => actionDelegate.isVisible(action, filter: filter)).map((action) { ...ChipAction.values.where((action) => actionDelegate.isVisible(action, filter: filter)).map((action) {
late String text; late String text;
switch (action) { switch (action) {
case ChipAction.reverse: case ChipAction.reverse:

View file

@ -92,7 +92,7 @@ class _OverlayCoordinateFilterChipState extends State<OverlayCoordinateFilterChi
useFilterColor: false, useFilterColor: false,
background: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred), background: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred),
maxWidth: double.infinity, maxWidth: double.infinity,
onTap: (filter) => FilterSelectedNotification(CoordinateFilter(bounds.sw, bounds.ne)).dispatch(context), onTap: (filter) => SelectFilterNotification(CoordinateFilter(bounds.sw, bounds.ne)).dispatch(context),
), ),
), ),
); );

View file

@ -97,7 +97,12 @@ class ExplorerActionDelegate with FeedbackMixin {
} }
void _hide(BuildContext context) { void _hide(BuildContext context) {
ChipActionDelegate().onActionSelected(context, _getPathFilter(), ChipAction.hide); final chipActionDelegate = ChipActionDelegate();
const action = ChipAction.hide;
final pathFilter = _getPathFilter();
if (chipActionDelegate.isVisible(action, filter: pathFilter)) {
chipActionDelegate.onActionSelected(context, pathFilter, action);
}
} }
void _goToStats(BuildContext context) { void _goToStats(BuildContext context) {

View file

@ -1,4 +1,7 @@
import 'package:aves/model/filters/covered/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/stored_album.dart';
import 'package:aves/model/filters/covered/tag.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/path.dart';
import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/rating.dart';
@ -15,6 +18,7 @@ import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/places_page.dart'; import 'package:aves/widgets/filter_grids/places_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -26,12 +30,21 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin {
}) { }) {
switch (action) { switch (action) {
case ChipAction.goToAlbumPage: case ChipAction.goToAlbumPage:
return filter is AlbumBaseFilter;
case ChipAction.goToCountryPage: case ChipAction.goToCountryPage:
return filter is LocationFilter && filter.level == LocationLevel.country;
case ChipAction.goToPlacePage: case ChipAction.goToPlacePage:
return filter is LocationFilter && filter.level == LocationLevel.place;
case ChipAction.goToTagPage: case ChipAction.goToTagPage:
return filter is TagFilter;
case ChipAction.goToExplorerPage: case ChipAction.goToExplorerPage:
return filter is StoredAlbumFilter || filter is PathFilter;
case ChipAction.ratingOrGreater: case ChipAction.ratingOrGreater:
return filter is RatingFilter && 1 < filter.rating && filter.rating < 5 && filter.op != RatingFilter.opOrGreater;
case ChipAction.ratingOrLower: case ChipAction.ratingOrLower:
return filter is RatingFilter && 1 < filter.rating && filter.rating < 5 && filter.op != RatingFilter.opOrLower;
case ChipAction.decompose:
return filter is DynamicAlbumFilter;
case ChipAction.reverse: case ChipAction.reverse:
return true; return true;
case ChipAction.hide: case ChipAction.hide:
@ -69,11 +82,13 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin {
); );
} }
case ChipAction.ratingOrGreater: case ChipAction.ratingOrGreater:
FilterNotification((filter as RatingFilter).copyWith(RatingFilter.opOrGreater)).dispatch(context); SelectFilterNotification((filter as RatingFilter).copyWith(RatingFilter.opOrGreater)).dispatch(context);
case ChipAction.ratingOrLower: case ChipAction.ratingOrLower:
FilterNotification((filter as RatingFilter).copyWith(RatingFilter.opOrLower)).dispatch(context); SelectFilterNotification((filter as RatingFilter).copyWith(RatingFilter.opOrLower)).dispatch(context);
case ChipAction.decompose:
DecomposeFilterNotification(filter).dispatch(context);
case ChipAction.reverse: case ChipAction.reverse:
FilterNotification(filter.reverse()).dispatch(context); SelectFilterNotification(filter.reverse()).dispatch(context);
case ChipAction.hide: case ChipAction.hide:
_hide(context, filter); _hide(context, filter);
case ChipAction.lockVault: case ChipAction.lockVault:
@ -119,10 +134,3 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin {
settings.changeFilterVisibility({filter}, false); settings.changeFilterVisibility({filter}, false);
} }
} }
@immutable
class FilterNotification extends Notification {
final CollectionFilter filter;
const FilterNotification(this.filter);
}

View file

@ -4,8 +4,8 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/entry/extensions/location.dart';
import 'package:aves/model/filters/coordinate.dart'; import 'package:aves/model/filters/coordinate.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/covered/location.dart'; import 'package:aves/model/filters/covered/location.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/highlight.dart'; import 'package:aves/model/highlight.dart';
import 'package:aves/model/media/geotiff.dart'; import 'package:aves/model/media/geotiff.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart';
@ -31,7 +31,6 @@ import 'package:aves/widgets/common/map/map_action_delegate.dart';
import 'package:aves/widgets/common/providers/highlight_info_provider.dart'; import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
import 'package:aves/widgets/common/providers/map_theme_provider.dart'; import 'package:aves/widgets/common/providers/map_theme_provider.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
import 'package:aves/widgets/map/scroller.dart'; import 'package:aves/widgets/map/scroller.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves/widgets/viewer/entry_viewer_page.dart';
@ -203,9 +202,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
Widget build(BuildContext context) { Widget build(BuildContext context) {
return NotificationListener( return NotificationListener(
onNotification: (notification) { onNotification: (notification) {
if (notification is FilterSelectedNotification) { if (notification is SelectFilterNotification) {
_goToCollection(notification.filter);
} else if (notification is FilterNotification) {
_goToCollection(notification.filter); _goToCollection(notification.filter);
} else if (notification is OpenMapAppNotification) { } else if (notification is OpenMapAppNotification) {
_openMapApp(); _openMapApp();

View file

@ -1,16 +1,18 @@
import 'package:aves/model/dynamic_albums.dart'; import 'package:aves/model/dynamic_albums.dart';
import 'package:aves/model/filters/covered/stored_album.dart';
import 'package:aves/model/filters/aspect_ratio.dart'; import 'package:aves/model/filters/aspect_ratio.dart';
import 'package:aves/model/filters/covered/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';
import 'package:aves/model/filters/date.dart'; import 'package:aves/model/filters/date.dart';
import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/covered/location.dart';
import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/missing.dart'; import 'package:aves/model/filters/missing.dart';
import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/recent.dart'; import 'package:aves/model/filters/recent.dart';
import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/set_and.dart';
import 'package:aves/model/filters/type.dart'; import 'package:aves/model/filters/type.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/album.dart';
@ -29,7 +31,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/common/search/delegate.dart'; import 'package:aves/widgets/common/search/delegate.dart';
import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/common/search/page.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -91,10 +93,21 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va
final upQuery = query.trim().toUpperCase(); final upQuery = query.trim().toUpperCase();
bool containQuery(String s) => s.toUpperCase().contains(upQuery); bool containQuery(String s) => s.toUpperCase().contains(upQuery);
return SafeArea( return SafeArea(
child: NotificationListener<FilterNotification>( child: NotificationListener(
onNotification: (notification) { onNotification: (notification) {
_select(context, notification.filter); if (notification is SelectFilterNotification) {
_select(context, {notification.filter});
return true; return true;
} else if (notification is DecomposeFilterNotification) {
final filter = notification.filter;
if (filter is DynamicAlbumFilter) {
final innerFilter = filter.filter;
final newFilters = innerFilter is SetAndFilter ? innerFilter.innerFilters : {innerFilter};
_select(context, newFilters);
return true;
}
}
return false;
}, },
child: ValueListenableBuilder<String?>( child: ValueListenableBuilder<String?>(
valueListenable: _expandedSectionNotifier, valueListenable: _expandedSectionNotifier,
@ -159,7 +172,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va
required List<CollectionFilter> filters, required List<CollectionFilter> filters,
HeroType Function(CollectionFilter filter)? heroTypeBuilder, HeroType Function(CollectionFilter filter)? heroTypeBuilder,
}) { }) {
void onTap(filter) => _select(context, filter is QueryFilter ? QueryFilter(filter.query) : filter); void onTap(filter) => _select(context, {filter is QueryFilter ? QueryFilter(filter.query) : filter});
const onLongPress = AvesFilterChip.showDefaultLongPressMenu; const onLongPress = AvesFilterChip.showDefaultLongPressMenu;
return title != null return title != null
? TitledExpandableFilterRow( ? TitledExpandableFilterRow(
@ -303,7 +316,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va
// `buildResults` is called in the build phase, // `buildResults` is called in the build phase,
// so we post the call that will filter the collection // so we post the call that will filter the collection
// and possibly trigger a rebuild here // and possibly trigger a rebuild here
_select(context, _buildQueryFilter(true)); _select(context, {_buildQueryFilter(true)});
}); });
} }
return const SizedBox(); return const SizedBox();
@ -314,12 +327,14 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va
return cleanQuery.isNotEmpty ? QueryFilter(cleanQuery, colorful: colorful) : null; return cleanQuery.isNotEmpty ? QueryFilter(cleanQuery, colorful: colorful) : null;
} }
Future<void> _select(BuildContext context, CollectionFilter? filter) async { Future<void> _select(BuildContext context, Set<CollectionFilter?> filters) async {
if (filter == null) { final newFilters = filters.nonNulls.toSet();
if (newFilters.isEmpty) {
goBack(context); goBack(context);
return; return;
} }
for (final filter in newFilters) {
if (!await unlockFilter(context, filter)) return; if (!await unlockFilter(context, filter)) return;
if (settings.saveSearchHistory) { if (settings.saveSearchHistory) {
@ -328,15 +343,17 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va
..insert(0, filter); ..insert(0, filter);
settings.searchHistory = history.take(searchHistoryCount).toList(); settings.searchHistory = history.take(searchHistoryCount).toList();
} }
}
if (parentCollection != null) { if (parentCollection != null) {
_applyToParentCollectionPage(context, filter); _applyToParentCollectionPage(context, newFilters);
} else { } else {
_jumpToCollectionPage(context, {filter}); _jumpToCollectionPage(context, newFilters);
} }
} }
void _applyToParentCollectionPage(BuildContext context, CollectionFilter filter) { void _applyToParentCollectionPage(BuildContext context, Set<CollectionFilter> filters) {
parentCollection!.addFilter(filter); parentCollection!.addFilters(filters);
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
// We delay closing the current page after applying the filter selection // We delay closing the current page after applying the filter selection
// so that hero animation target is ready in the `FilterBar`, // so that hero animation target is ready in the `FilterBar`,

View file

@ -1,11 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/model/entry/entry.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/covered/location.dart'; import 'package:aves/model/filters/covered/location.dart';
import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/covered/stored_album.dart';
import 'package:aves/model/filters/covered/tag.dart'; import 'package:aves/model/filters/covered/tag.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
@ -24,11 +24,11 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/extensions/media_query.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
import 'package:aves/widgets/stats/date/histogram.dart'; import 'package:aves/widgets/stats/date/histogram.dart';
import 'package:aves/widgets/stats/filter_table.dart'; import 'package:aves/widgets/stats/filter_table.dart';
import 'package:aves/widgets/stats/mime_donut.dart'; import 'package:aves/widgets/stats/mime_donut.dart';
import 'package:aves/widgets/stats/percent_text.dart'; import 'package:aves/widgets/stats/percent_text.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
@ -172,7 +172,7 @@ class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMix
], ],
), ),
); );
child = NotificationListener<FilterNotification>( child = NotificationListener<SelectFilterNotification>(
onNotification: (notification) { onNotification: (notification) {
_onFilterSelection(context, notification.filter); _onFilterSelection(context, notification.filter);
return true; return true;
@ -336,7 +336,7 @@ class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMix
} }
void _applyToParentCollectionPage(BuildContext context, CollectionFilter filter) { void _applyToParentCollectionPage(BuildContext context, CollectionFilter filter) {
widget.parentCollection!.addFilter(filter); widget.parentCollection!.addFilters({filter});
// We delay closing the current page after applying the filter selection // We delay closing the current page after applying the filter selection
// so that hero animation target is ready in the `FilterBar`, // so that hero animation target is ready in the `FilterBar`,
// even when the target is a child of an `AnimatedList`. // even when the target is a child of an `AnimatedList`.
@ -384,7 +384,7 @@ class StatsTopPage extends StatelessWidget {
child: SafeArea( child: SafeArea(
bottom: false, bottom: false,
child: Builder(builder: (context) { child: Builder(builder: (context) {
return NotificationListener<FilterNotification>( return NotificationListener<SelectFilterNotification>(
onNotification: (notification) { onNotification: (notification) {
onFilterSelection(notification.filter); onFilterSelection(notification.filter);
return true; return true;

View file

@ -89,13 +89,23 @@ class CastNotification extends Notification with EquatableMixin {
} }
@immutable @immutable
class FilterSelectedNotification extends Notification with EquatableMixin { class SelectFilterNotification extends Notification with EquatableMixin {
final CollectionFilter filter; final CollectionFilter filter;
@override @override
List<Object?> get props => [filter]; List<Object?> get props => [filter];
const FilterSelectedNotification(this.filter); const SelectFilterNotification(this.filter);
}
@immutable
class DecomposeFilterNotification extends Notification with EquatableMixin {
final CollectionFilter filter;
@override
List<Object?> get props => [filter];
const DecomposeFilterNotification(this.filter);
} }
@immutable @immutable

View file

@ -564,7 +564,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} }
bool _handleNotification(dynamic notification) { bool _handleNotification(dynamic notification) {
if (notification is FilterSelectedNotification) { if (notification is SelectFilterNotification) {
_goToCollection(notification.filter); _goToCollection(notification.filter);
} else if (notification is CastNotification) { } else if (notification is CastNotification) {
_cast(notification.enabled); _cast(notification.enabled);

View file

@ -10,7 +10,6 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/basic/tv_edge_focus.dart'; import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart'; import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves/widgets/viewer/info/basic_section.dart'; import 'package:aves/widgets/viewer/info/basic_section.dart';
@ -235,7 +234,7 @@ class _InfoPageContentState extends State<_InfoPageContent> {
), ),
); );
return NotificationListener<FilterNotification>( return NotificationListener<SelectFilterNotification>(
onNotification: (notification) { onNotification: (notification) {
_onFilter(notification.filter); _onFilter(notification.filter);
return true; return true;
@ -288,6 +287,6 @@ class _InfoPageContentState extends State<_InfoPageContent> {
void _onFilter(CollectionFilter filter) { void _onFilter(CollectionFilter filter) {
if (!mounted) return; if (!mounted) return;
FilterSelectedNotification(filter).dispatch(context); SelectFilterNotification(filter).dispatch(context);
} }
} }

View file

@ -6,6 +6,7 @@ enum ChipAction {
goToExplorerPage, goToExplorerPage,
ratingOrGreater, ratingOrGreater,
ratingOrLower, ratingOrLower,
decompose,
reverse, reverse,
hide, hide,
lockVault, lockVault,