From 4ee510d7a025e6b55843671c35805eaef60620ce Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 2 Nov 2021 10:06:21 +0900 Subject: [PATCH] harmonized action menus --- lib/model/actions/chip_set_actions.dart | 62 +++++-- lib/widgets/collection/app_bar.dart | 14 +- .../collection/entry_set_action_delegate.dart | 10 +- lib/widgets/filter_grids/albums_page.dart | 1 - .../common/action_delegates/album_set.dart | 57 +++++-- .../common/action_delegates/chip_set.dart | 136 +++++++++++---- lib/widgets/filter_grids/common/app_bar.dart | 160 +++++++----------- .../filter_grids/common/filter_nav_page.dart | 6 +- lib/widgets/search/search_button.dart | 37 ---- 9 files changed, 278 insertions(+), 205 deletions(-) delete mode 100644 lib/widgets/search/search_button.dart diff --git a/lib/model/actions/chip_set_actions.dart b/lib/model/actions/chip_set_actions.dart index 83a43d636..24d604796 100644 --- a/lib/model/actions/chip_set_actions.dart +++ b/lib/model/actions/chip_set_actions.dart @@ -1,6 +1,6 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; enum ChipSetAction { // general @@ -9,20 +9,50 @@ enum ChipSetAction { select, selectAll, selectNone, + // browsing + search, createAlbum, - // all or filter selection + // browsing or selecting map, stats, - // single/multiple filter selection + // selecting (single/multiple filters) delete, hide, pin, unpin, - // single filter selection + // selecting (single filter) rename, setCover, } +class ChipSetActions { + static const general = [ + ChipSetAction.sort, + ChipSetAction.group, + ChipSetAction.select, + ChipSetAction.selectAll, + ChipSetAction.selectNone, + ]; + + static const browsing = [ + ChipSetAction.search, + ChipSetAction.createAlbum, + ChipSetAction.map, + ChipSetAction.stats, + ]; + + static const selection = [ + ChipSetAction.setCover, + ChipSetAction.pin, + ChipSetAction.unpin, + ChipSetAction.delete, + ChipSetAction.rename, + ChipSetAction.hide, + ChipSetAction.map, + ChipSetAction.stats, + ]; +} + extension ExtraChipSetAction on ChipSetAction { String getText(BuildContext context) { switch (this) { @@ -37,13 +67,17 @@ extension ExtraChipSetAction on ChipSetAction { return context.l10n.menuActionSelectAll; case ChipSetAction.selectNone: return context.l10n.menuActionSelectNone; + // browsing + case ChipSetAction.search: + return MaterialLocalizations.of(context).searchFieldLabel; + case ChipSetAction.createAlbum: + return context.l10n.chipActionCreateAlbum; + // browsing or selecting case ChipSetAction.map: return context.l10n.menuActionMap; case ChipSetAction.stats: return context.l10n.menuActionStats; - case ChipSetAction.createAlbum: - return context.l10n.chipActionCreateAlbum; - // single/multiple filters + // selecting (single/multiple filters) case ChipSetAction.delete: return context.l10n.chipActionDelete; case ChipSetAction.hide: @@ -52,7 +86,7 @@ extension ExtraChipSetAction on ChipSetAction { return context.l10n.chipActionPin; case ChipSetAction.unpin: return context.l10n.chipActionUnpin; - // single filter + // selecting (single filter) case ChipSetAction.rename: return context.l10n.chipActionRename; case ChipSetAction.setCover: @@ -77,13 +111,17 @@ extension ExtraChipSetAction on ChipSetAction { return AIcons.selected; case ChipSetAction.selectNone: return AIcons.unselected; + // browsing + case ChipSetAction.search: + return AIcons.search; + case ChipSetAction.createAlbum: + return AIcons.add; + // browsing or selecting case ChipSetAction.map: return AIcons.map; case ChipSetAction.stats: return AIcons.stats; - case ChipSetAction.createAlbum: - return AIcons.add; - // single/multiple filters + // selecting (single/multiple filters) case ChipSetAction.delete: return AIcons.delete; case ChipSetAction.hide: @@ -92,7 +130,7 @@ extension ExtraChipSetAction on ChipSetAction { return AIcons.pin; case ChipSetAction.unpin: return AIcons.unpin; - // single filter + // selecting (single filter) case ChipSetAction.rename: return AIcons.rename; case ChipSetAction.setCover: diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 8e72df6d5..33533ebdc 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -201,12 +201,12 @@ class _CollectionAppBarState extends State with SingleTickerPr final browsingQuickActions = settings.collectionBrowsingQuickActions; final selectionQuickActions = settings.collectionSelectionQuickActions; - final quickActions = (isSelecting ? selectionQuickActions : browsingQuickActions).where(isVisible).map( + final quickActionButtons = (isSelecting ? selectionQuickActions : browsingQuickActions).where(isVisible).map( (action) => _toActionButton(action, enabled: canApply(action)), ); return [ - ...quickActions, + ...quickActionButtons, MenuIconTheme( child: PopupMenuButton( // key is expected by test driver @@ -252,7 +252,7 @@ class _CollectionAppBarState extends State with SingleTickerPr onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action await Future.delayed(Durations.popupMenuAnimation * timeDilation); - await _onCollectionActionSelected(action); + await _onActionSelected(action); }, ), ), @@ -262,16 +262,16 @@ class _CollectionAppBarState extends State with SingleTickerPr // key is expected by test driver (e.g. 'menu-sort', 'menu-group', 'menu-map') Key _getActionKey(EntrySetAction action) => Key('menu-${action.toString().substring('EntrySetAction.'.length)}'); - Widget _toActionButton(EntrySetAction action, {bool enabled = true}) { + Widget _toActionButton(EntrySetAction action, {required bool enabled}) { return IconButton( key: _getActionKey(action), icon: action.getIcon(), - onPressed: enabled ? () => _onCollectionActionSelected(action) : null, + onPressed: enabled ? () => _onActionSelected(action) : null, tooltip: action.getText(context), ); } - PopupMenuItem _toMenuItem(EntrySetAction action, {bool enabled = true}) { + PopupMenuItem _toMenuItem(EntrySetAction action, {required bool enabled}) { return PopupMenuItem( key: _getActionKey(action), value: action, @@ -339,7 +339,7 @@ class _CollectionAppBarState extends State with SingleTickerPr } } - Future _onCollectionActionSelected(EntrySetAction action) async { + Future _onActionSelected(EntrySetAction action) async { switch (action) { // general case EntrySetAction.sort: diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index b5a99bcbd..4597268a8 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -151,13 +151,13 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa _share(context); break; case EntrySetAction.delete: - _showDeleteDialog(context); + _delete(context); break; case EntrySetAction.copy: - _moveSelection(context, moveType: MoveType.copy); + _move(context, moveType: MoveType.copy); break; case EntrySetAction.move: - _moveSelection(context, moveType: MoveType.move); + _move(context, moveType: MoveType.move); break; case EntrySetAction.rescan: _rescan(context); @@ -203,7 +203,7 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa selection.browse(); } - Future _showDeleteDialog(BuildContext context) async { + Future _delete(BuildContext context) async { final source = context.read(); final selection = context.read>(); final selectedItems = _getExpandedSelectedItems(selection); @@ -256,7 +256,7 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa ); } - Future _moveSelection(BuildContext context, {required MoveType moveType}) async { + Future _move(BuildContext context, {required MoveType moveType}) async { final l10n = context.l10n; final source = context.read(); final selection = context.read>(); diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index 4e798ed86..e82640bd2 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -42,7 +42,6 @@ class AlbumListPage extends StatelessWidget { source: source, title: context.l10n.albumPageTitle, sortFactor: settings.albumSortFactor, - groupable: true, showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none, actionDelegate: AlbumChipSetActionDelegate(gridItems), filterSections: groupToSections(context, source, gridItems), diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index e0ead6a48..606c928a8 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -1,10 +1,12 @@ import 'dart:io'; +import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/chip_set_actions.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/highlight.dart'; +import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; @@ -39,29 +41,54 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate { set sortFactor(ChipSortFactor factor) => settings.albumSortFactor = factor; @override - bool isVisible(ChipSetAction action, Set filters) { + bool isVisible( + ChipSetAction action, { + required AppMode appMode, + required bool isSelecting, + required int itemCount, + required Set selectedFilters, + }) { switch (action) { + case ChipSetAction.group: + return true; case ChipSetAction.createAlbum: + return appMode == AppMode.main && !isSelecting; case ChipSetAction.delete: case ChipSetAction.rename: - return true; + return appMode == AppMode.main && isSelecting; default: - return super.isVisible(action, filters); + return super.isVisible( + action, + appMode: appMode, + isSelecting: isSelecting, + itemCount: itemCount, + selectedFilters: selectedFilters, + ); } } @override - bool canApply(ChipSetAction action, Set filters) { + bool canApply( + ChipSetAction action, { + required bool isSelecting, + required int itemCount, + required Set selectedFilters, + }) { switch (action) { case ChipSetAction.rename: { - if (filters.length != 1) return false; + if (selectedFilters.length != 1) return false; // do not allow renaming volume root - final dir = VolumeRelativeDirectory.fromPath(filters.first.album); + final dir = VolumeRelativeDirectory.fromPath(selectedFilters.first.album); return dir != null && dir.relativeDir.isNotEmpty; } default: - return super.canApply(action, filters); + return super.canApply( + action, + isSelecting: isSelecting, + itemCount: itemCount, + selectedFilters: selectedFilters, + ); } } @@ -70,18 +97,18 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate { switch (action) { // general case ChipSetAction.group: - _showGroupDialog(context); + _group(context); break; case ChipSetAction.createAlbum: _createAlbum(context); break; // single/multiple filters case ChipSetAction.delete: - _showDeleteDialog(context, filters); + _delete(context, filters); break; // single filter case ChipSetAction.rename: - _showRenameDialog(context, filters.first); + _rename(context, filters.first); break; default: break; @@ -89,7 +116,9 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate { super.onActionSelected(context, filters, action); } - Future _showGroupDialog(BuildContext context) async { + void _browse(BuildContext context) => context.read>>().browse(); + + Future _group(BuildContext context) async { final factor = await showDialog( context: context, builder: (context) => AvesSelectionDialog( @@ -129,7 +158,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate { } } - Future _showDeleteDialog(BuildContext context, Set filters) async { + Future _delete(BuildContext context, Set filters) async { final l10n = context.l10n; final messenger = ScaffoldMessenger.of(context); final source = context.read(); @@ -173,6 +202,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate { onDone: (processed) async { final deletedUris = processed.where((event) => event.success).map((event) => event.uri).toSet(); await source.removeEntries(deletedUris); + _browse(context); source.resumeMonitoring(); final deletedCount = deletedUris.length; @@ -187,7 +217,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate { ); } - Future _showRenameDialog(BuildContext context, AlbumFilter filter) async { + Future _rename(BuildContext context, AlbumFilter filter) async { final l10n = context.l10n; final messenger = ScaffoldMessenger.of(context); final source = context.read(); @@ -238,6 +268,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate { onDone: (processed) async { final movedOps = processed.where((e) => e.success).toSet(); await source.renameAlbum(album, destinationAlbum, todoEntries, movedOps); + _browse(context); source.resumeMonitoring(); final movedCount = movedOps.length; diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index 4169036c4..bb02c7bf1 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -1,3 +1,4 @@ +import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/chip_set_actions.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/entry.dart'; @@ -16,6 +17,7 @@ import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/dialogs/cover_selection_dialog.dart'; import 'package:aves/widgets/map/map_page.dart'; +import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/stats/stats_page.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -30,23 +32,63 @@ abstract class ChipSetActionDelegate with FeedbackMi set sortFactor(ChipSortFactor factor); - bool isVisible(ChipSetAction action, Set filters) { - final hasSelection = filters.isNotEmpty; + bool isVisible( + ChipSetAction action, { + required AppMode appMode, + required bool isSelecting, + required int itemCount, + required Set selectedFilters, + }) { + final selectedItemCount = selectedFilters.length; + final hasSelection = selectedFilters.isNotEmpty; switch (action) { + // general + case ChipSetAction.sort: + return true; + case ChipSetAction.group: + return false; + case ChipSetAction.select: + return appMode.canSelect && !isSelecting; + case ChipSetAction.selectAll: + return isSelecting && selectedItemCount < itemCount; + case ChipSetAction.selectNone: + return isSelecting && selectedItemCount == itemCount; + // browsing + case ChipSetAction.search: + return appMode.canSearch && !isSelecting; case ChipSetAction.createAlbum: + return false; + // browsing or selecting + case ChipSetAction.map: + case ChipSetAction.stats: + return appMode == AppMode.main; + // selecting (single/multiple filters) case ChipSetAction.delete: + return false; + case ChipSetAction.hide: + return appMode == AppMode.main; + case ChipSetAction.pin: + return !hasSelection || !settings.pinnedFilters.containsAll(selectedFilters); + case ChipSetAction.unpin: + return hasSelection && settings.pinnedFilters.containsAll(selectedFilters); + // selecting (single filter) case ChipSetAction.rename: return false; - case ChipSetAction.pin: - return !hasSelection || !settings.pinnedFilters.containsAll(filters); - case ChipSetAction.unpin: - return hasSelection && settings.pinnedFilters.containsAll(filters); - default: - return true; + case ChipSetAction.setCover: + return appMode == AppMode.main; } } - bool canApply(ChipSetAction action, Set filters) { + bool canApply( + ChipSetAction action, { + required bool isSelecting, + required int itemCount, + required Set selectedFilters, + }) { + final selectedItemCount = selectedFilters.length; + final hasItems = itemCount > 0; + final hasSelection = selectedItemCount > 0; + switch (action) { // general case ChipSetAction.sort: @@ -54,20 +96,24 @@ abstract class ChipSetActionDelegate with FeedbackMi case ChipSetAction.select: case ChipSetAction.selectAll: case ChipSetAction.selectNone: - case ChipSetAction.map: - case ChipSetAction.stats: + // browsing + case ChipSetAction.search: case ChipSetAction.createAlbum: return true; - // single/multiple filters + // browsing or selecting + case ChipSetAction.map: + case ChipSetAction.stats: + return (!isSelecting && hasItems) || (isSelecting && hasSelection); + // selecting (single/multiple filters) case ChipSetAction.delete: case ChipSetAction.hide: case ChipSetAction.pin: case ChipSetAction.unpin: - return filters.isNotEmpty; - // single filter + return hasSelection; + // selecting (single filter) case ChipSetAction.rename: case ChipSetAction.setCover: - return filters.length == 1; + return selectedItemCount == 1; } } @@ -77,11 +123,7 @@ abstract class ChipSetActionDelegate with FeedbackMi case ChipSetAction.sort: _showSortDialog(context); break; - case ChipSetAction.map: - _goToMap(context, filters); - break; - case ChipSetAction.stats: - _goToStats(context, filters); + case ChipSetAction.group: break; case ChipSetAction.select: context.read>>().select(); @@ -92,25 +134,44 @@ abstract class ChipSetActionDelegate with FeedbackMi case ChipSetAction.selectNone: context.read>>().clearSelection(); break; - // single/multiple filters - case ChipSetAction.pin: - settings.pinnedFilters = settings.pinnedFilters..addAll(filters); + // browsing + case ChipSetAction.search: + _goToSearch(context); break; - case ChipSetAction.unpin: - settings.pinnedFilters = settings.pinnedFilters..removeAll(filters); + case ChipSetAction.createAlbum: + break; + // browsing or selecting + case ChipSetAction.map: + _goToMap(context, filters); + break; + case ChipSetAction.stats: + _goToStats(context, filters); + break; + // selecting (single/multiple filters) + case ChipSetAction.delete: break; case ChipSetAction.hide: _hide(context, filters); break; - // single filter - case ChipSetAction.setCover: - _showCoverSelectionDialog(context, filters.first); + case ChipSetAction.pin: + settings.pinnedFilters = settings.pinnedFilters..addAll(filters); + _browse(context); break; - default: + case ChipSetAction.unpin: + settings.pinnedFilters = settings.pinnedFilters..removeAll(filters); + _browse(context); + break; + // selecting (single filter) + case ChipSetAction.rename: + break; + case ChipSetAction.setCover: + _setCover(context, filters.first); break; } } + void _browse(BuildContext context) => context.read>>().browse(); + Iterable _selectedEntries(BuildContext context, Set filters) { final source = context.read(); final visibleEntries = source.visibleEntries; @@ -167,6 +228,17 @@ abstract class ChipSetActionDelegate with FeedbackMi ); } + void _goToSearch(BuildContext context) { + Navigator.push( + context, + SearchPageRoute( + delegate: CollectionSearchDelegate( + source: context.read(), + ), + ), + ); + } + Future _hide(BuildContext context, Set filters) async { final confirmed = await showDialog( context: context, @@ -191,9 +263,11 @@ abstract class ChipSetActionDelegate with FeedbackMi final source = context.read(); source.changeFilterVisibility(filters, false); + + _browse(context); } - void _showCoverSelectionDialog(BuildContext context, T filter) async { + void _setCover(BuildContext context, T filter) async { final contentId = covers.coverContentId(filter); final customEntry = context.read().visibleEntries.firstWhereOrNull((entry) => entry.contentId == contentId); final coverSelection = await showDialog>( @@ -207,5 +281,7 @@ abstract class ChipSetActionDelegate with FeedbackMi final isCustom = coverSelection.item1; await covers.set(filter, isCustom ? coverSelection.item2?.contentId : null); + + _browse(context); } } diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index c8d8fdd87..c47e56b37 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -11,7 +11,6 @@ import 'package:aves/widgets/common/app_bar_title.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; -import 'package:aves/widgets/search/search_button.dart'; import 'package:aves/widgets/search/search_delegate.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -21,15 +20,14 @@ import 'package:provider/provider.dart'; class FilterGridAppBar extends StatefulWidget { final CollectionSource source; final String title; - final ChipSetActionDelegate actionDelegate; - final bool groupable, isEmpty; + final ChipSetActionDelegate actionDelegate; + final bool isEmpty; const FilterGridAppBar({ Key? key, required this.source, required this.title, required this.actionDelegate, - required this.groupable, required this.isEmpty, }) : super(key: key); @@ -45,15 +43,14 @@ class _FilterGridAppBarState extends State widget.actionDelegate; - static const filterSelectionActions = [ + static const browsingQuickActions = [ + ChipSetAction.search, + ]; + static const selectionQuickActions = [ ChipSetAction.setCover, ChipSetAction.pin, ChipSetAction.unpin, - ChipSetAction.delete, - ChipSetAction.rename, - ChipSetAction.hide, ]; - static const buttonActionCount = 2; @override void initState() { @@ -128,113 +125,78 @@ class _FilterGridAppBarState extends State _buildActions(AppMode appMode, Selection> selection) { - final selectedFilters = selection.selectedItems.map((v) => v.filter).toSet(); - - PopupMenuItem toMenuItem(ChipSetAction action, {bool enabled = true}) { - return PopupMenuItem( - value: action, - enabled: enabled && actionDelegate.canApply(action, selectedFilters), - child: MenuRow(text: action.getText(context), icon: action.getIcon()), - ); - } - - void applyAction(ChipSetAction action) { - actionDelegate.onActionSelected(context, selectedFilters, action); - if (filterSelectionActions.contains(action)) { - selection.browse(); - } - } - + final itemCount = actionDelegate.allItems.length; final isSelecting = selection.isSelecting; - final selectionRowActions = []; + final selectedItems = selection.selectedItems; + final selectedFilters = selectedItems.map((v) => v.filter).toSet(); - final buttonActions = []; - if (isSelecting) { - final selectedFilters = selection.selectedItems.map((v) => v.filter).toSet(); - final visibleActions = filterSelectionActions.where((action) => actionDelegate.isVisible(action, selectedFilters)).toList(); - buttonActions.addAll(visibleActions.take(buttonActionCount).map( - (action) { - final enabled = actionDelegate.canApply(action, selectedFilters); - return IconButton( - icon: action.getIcon(), - onPressed: enabled ? () => applyAction(action) : null, - tooltip: action.getText(context), - ); - }, - )); - selectionRowActions.addAll(visibleActions.skip(buttonActionCount)); - } else if (appMode.canSearch) { - buttonActions.add(CollectionSearchButton(source: source)); - } + bool isVisible(ChipSetAction action) => actionDelegate.isVisible( + action, + appMode: appMode, + isSelecting: isSelecting, + itemCount: itemCount, + selectedFilters: selectedFilters, + ); + bool canApply(ChipSetAction action) => actionDelegate.canApply( + action, + isSelecting: isSelecting, + itemCount: itemCount, + selectedFilters: selectedFilters, + ); + + final quickActionButtons = (isSelecting ? selectionQuickActions : browsingQuickActions).where(isVisible).map( + (action) => _toActionButton(action, enabled: canApply(action)), + ); return [ - ...buttonActions, + ...quickActionButtons, MenuIconTheme( child: PopupMenuButton( itemBuilder: (context) { - final selectedItems = selection.selectedItems; - final hasSelection = selectedItems.isNotEmpty; - final hasItems = !widget.isEmpty; - final otherViewEnabled = (!isSelecting && hasItems) || (isSelecting && hasSelection); + final generalMenuItems = ChipSetActions.general.where(isVisible).map( + (action) => _toMenuItem(action, enabled: canApply(action)), + ); - final menuItems = >[ - toMenuItem(ChipSetAction.sort), - if (widget.groupable) toMenuItem(ChipSetAction.group), - if (appMode == AppMode.main && !isSelecting) - toMenuItem( - ChipSetAction.select, - enabled: hasItems, - ), - ]; + final browsingMenuActions = ChipSetActions.browsing.where((v) => !browsingQuickActions.contains(v)); + final selectionMenuActions = ChipSetActions.selection.where((v) => !selectionQuickActions.contains(v)); + final contextualMenuItems = (isSelecting ? selectionMenuActions : browsingMenuActions).where(isVisible).map( + (action) => _toMenuItem(action, enabled: canApply(action)), + ); - if (appMode == AppMode.main) { - menuItems.add(const PopupMenuDivider()); - if (isSelecting) { - menuItems.addAll(selectionRowActions.map(toMenuItem)); - } - menuItems.addAll([ - toMenuItem( - ChipSetAction.map, - enabled: otherViewEnabled, - ), - toMenuItem( - ChipSetAction.stats, - enabled: otherViewEnabled, - ), - ]); - if (!isSelecting && actionDelegate.isVisible(ChipSetAction.createAlbum, selectedFilters)) { - menuItems.addAll([ - const PopupMenuDivider(), - toMenuItem(ChipSetAction.createAlbum), - ]); - } - } - if (isSelecting) { - menuItems.addAll([ + return [ + ...generalMenuItems, + if (contextualMenuItems.isNotEmpty) ...[ const PopupMenuDivider(), - toMenuItem( - ChipSetAction.selectAll, - enabled: selectedItems.length < actionDelegate.allItems.length, - ), - toMenuItem( - ChipSetAction.selectNone, - enabled: hasSelection, - ), - ]); - } - - return menuItems; + ...contextualMenuItems, + ], + ]; }, onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action await Future.delayed(Durations.popupMenuAnimation * timeDilation); - applyAction(action); + _onActionSelected(action); }, ), ), ]; } + Widget _toActionButton(ChipSetAction action, {required bool enabled}) { + return IconButton( + icon: action.getIcon(), + onPressed: enabled ? () => _onActionSelected(action) : null, + tooltip: action.getText(context), + ); + } + + PopupMenuItem _toMenuItem(ChipSetAction action, {required bool enabled}) { + return PopupMenuItem( + value: action, + enabled: enabled, + child: MenuRow(text: action.getText(context), icon: action.getIcon()), + ); + } + void _onActivityChange() { if (context.read>>().isSelecting) { _browseToSelectAnimation.forward(); @@ -243,6 +205,12 @@ class _FilterGridAppBarState extends State>>(); + final selectedFilters = selection.selectedItems.map((v) => v.filter).toSet(); + actionDelegate.onActionSelected(context, selectedFilters, action); + } + void _goToSearch() { Navigator.push( context, diff --git a/lib/widgets/filter_grids/common/filter_nav_page.dart b/lib/widgets/filter_grids/common/filter_nav_page.dart index 65b2d36df..f256d8601 100644 --- a/lib/widgets/filter_grids/common/filter_nav_page.dart +++ b/lib/widgets/filter_grids/common/filter_nav_page.dart @@ -15,8 +15,8 @@ class FilterNavigationPage extends StatelessWidget { final CollectionSource source; final String title; final ChipSortFactor sortFactor; - final bool groupable, showHeaders; - final ChipSetActionDelegate actionDelegate; + final bool showHeaders; + final ChipSetActionDelegate actionDelegate; final Map>> filterSections; final Set? newFilters; final Widget Function() emptyBuilder; @@ -26,7 +26,6 @@ class FilterNavigationPage extends StatelessWidget { required this.source, required this.title, required this.sortFactor, - this.groupable = false, this.showHeaders = false, required this.actionDelegate, required this.filterSections, @@ -43,7 +42,6 @@ class FilterNavigationPage extends StatelessWidget { source: source, title: title, actionDelegate: actionDelegate, - groupable: groupable, isEmpty: filterSections.isEmpty, ), sections: filterSections, diff --git a/lib/widgets/search/search_button.dart b/lib/widgets/search/search_button.dart deleted file mode 100644 index 01bd46abc..000000000 --- a/lib/widgets/search/search_button.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/theme/icons.dart'; -import 'package:aves/widgets/search/search_delegate.dart'; -import 'package:flutter/material.dart'; - -class CollectionSearchButton extends StatelessWidget { - final CollectionSource source; - final CollectionLens? parentCollection; - - const CollectionSearchButton({ - Key? key, - required this.source, - this.parentCollection, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return IconButton( - icon: const Icon(AIcons.search), - onPressed: () => _goToSearch(context), - tooltip: MaterialLocalizations.of(context).searchFieldLabel, - ); - } - - void _goToSearch(BuildContext context) { - Navigator.push( - context, - SearchPageRoute( - delegate: CollectionSearchDelegate( - source: source, - parentCollection: parentCollection, - ), - ), - ); - } -}