harmonized action menus
This commit is contained in:
parent
2b90d7cca8
commit
4ee510d7a0
9 changed files with 278 additions and 205 deletions
|
@ -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:
|
||||
|
|
|
@ -201,12 +201,12 @@ class _CollectionAppBarState extends State<CollectionAppBar> 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<EntrySetAction>(
|
||||
// key is expected by test driver
|
||||
|
@ -252,7 +252,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> 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<CollectionAppBar> 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<EntrySetAction> _toMenuItem(EntrySetAction action, {bool enabled = true}) {
|
||||
PopupMenuItem<EntrySetAction> _toMenuItem(EntrySetAction action, {required bool enabled}) {
|
||||
return PopupMenuItem(
|
||||
key: _getActionKey(action),
|
||||
value: action,
|
||||
|
@ -339,7 +339,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _onCollectionActionSelected(EntrySetAction action) async {
|
||||
Future<void> _onActionSelected(EntrySetAction action) async {
|
||||
switch (action) {
|
||||
// general
|
||||
case EntrySetAction.sort:
|
||||
|
|
|
@ -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<void> _showDeleteDialog(BuildContext context) async {
|
||||
Future<void> _delete(BuildContext context) async {
|
||||
final source = context.read<CollectionSource>();
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
final selectedItems = _getExpandedSelectedItems(selection);
|
||||
|
@ -256,7 +256,7 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _moveSelection(BuildContext context, {required MoveType moveType}) async {
|
||||
Future<void> _move(BuildContext context, {required MoveType moveType}) async {
|
||||
final l10n = context.l10n;
|
||||
final source = context.read<CollectionSource>();
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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<AlbumFilter> {
|
|||
set sortFactor(ChipSortFactor factor) => settings.albumSortFactor = factor;
|
||||
|
||||
@override
|
||||
bool isVisible(ChipSetAction action, Set<AlbumFilter> filters) {
|
||||
bool isVisible(
|
||||
ChipSetAction action, {
|
||||
required AppMode appMode,
|
||||
required bool isSelecting,
|
||||
required int itemCount,
|
||||
required Set<AlbumFilter> 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<AlbumFilter> filters) {
|
||||
bool canApply(
|
||||
ChipSetAction action, {
|
||||
required bool isSelecting,
|
||||
required int itemCount,
|
||||
required Set<AlbumFilter> 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<AlbumFilter> {
|
|||
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<AlbumFilter> {
|
|||
super.onActionSelected(context, filters, action);
|
||||
}
|
||||
|
||||
Future<void> _showGroupDialog(BuildContext context) async {
|
||||
void _browse(BuildContext context) => context.read<Selection<FilterGridItem<AlbumFilter>>>().browse();
|
||||
|
||||
Future<void> _group(BuildContext context) async {
|
||||
final factor = await showDialog<AlbumChipGroupFactor>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<AlbumChipGroupFactor>(
|
||||
|
@ -129,7 +158,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _showDeleteDialog(BuildContext context, Set<AlbumFilter> filters) async {
|
||||
Future<void> _delete(BuildContext context, Set<AlbumFilter> filters) async {
|
||||
final l10n = context.l10n;
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final source = context.read<CollectionSource>();
|
||||
|
@ -173,6 +202,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> {
|
|||
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<AlbumFilter> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _showRenameDialog(BuildContext context, AlbumFilter filter) async {
|
||||
Future<void> _rename(BuildContext context, AlbumFilter filter) async {
|
||||
final l10n = context.l10n;
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final source = context.read<CollectionSource>();
|
||||
|
@ -238,6 +268,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> {
|
|||
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;
|
||||
|
|
|
@ -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<T extends CollectionFilter> with FeedbackMi
|
|||
|
||||
set sortFactor(ChipSortFactor factor);
|
||||
|
||||
bool isVisible(ChipSetAction action, Set<T> filters) {
|
||||
final hasSelection = filters.isNotEmpty;
|
||||
bool isVisible(
|
||||
ChipSetAction action, {
|
||||
required AppMode appMode,
|
||||
required bool isSelecting,
|
||||
required int itemCount,
|
||||
required Set<T> 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<T> filters) {
|
||||
bool canApply(
|
||||
ChipSetAction action, {
|
||||
required bool isSelecting,
|
||||
required int itemCount,
|
||||
required Set<T> 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<T extends CollectionFilter> 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<T extends CollectionFilter> 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<Selection<FilterGridItem<T>>>().select();
|
||||
|
@ -92,25 +134,44 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
|||
case ChipSetAction.selectNone:
|
||||
context.read<Selection<FilterGridItem<T>>>().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<Selection<FilterGridItem<T>>>().browse();
|
||||
|
||||
Iterable<AvesEntry> _selectedEntries(BuildContext context, Set<dynamic> filters) {
|
||||
final source = context.read<CollectionSource>();
|
||||
final visibleEntries = source.visibleEntries;
|
||||
|
@ -167,6 +228,17 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
|||
);
|
||||
}
|
||||
|
||||
void _goToSearch(BuildContext context) {
|
||||
Navigator.push(
|
||||
context,
|
||||
SearchPageRoute(
|
||||
delegate: CollectionSearchDelegate(
|
||||
source: context.read<CollectionSource>(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _hide(BuildContext context, Set<T> filters) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
|
@ -191,9 +263,11 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
|||
|
||||
final source = context.read<CollectionSource>();
|
||||
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<CollectionSource>().visibleEntries.firstWhereOrNull((entry) => entry.contentId == contentId);
|
||||
final coverSelection = await showDialog<Tuple2<bool, AvesEntry?>>(
|
||||
|
@ -207,5 +281,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
|||
|
||||
final isCustom = coverSelection.item1;
|
||||
await covers.set(filter, isCustom ? coverSelection.item2?.contentId : null);
|
||||
|
||||
_browse(context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T extends CollectionFilter> extends StatefulWidget {
|
||||
final CollectionSource source;
|
||||
final String title;
|
||||
final ChipSetActionDelegate actionDelegate;
|
||||
final bool groupable, isEmpty;
|
||||
final ChipSetActionDelegate<T> 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<T extends CollectionFilter> extends State<FilterGri
|
|||
|
||||
ChipSetActionDelegate get actionDelegate => 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<T extends CollectionFilter> extends State<FilterGri
|
|||
}
|
||||
|
||||
List<Widget> _buildActions(AppMode appMode, Selection<FilterGridItem<T>> selection) {
|
||||
final selectedFilters = selection.selectedItems.map((v) => v.filter).toSet();
|
||||
|
||||
PopupMenuItem<ChipSetAction> 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 = <ChipSetAction>[];
|
||||
final selectedItems = selection.selectedItems;
|
||||
final selectedFilters = selectedItems.map((v) => v.filter).toSet();
|
||||
|
||||
final buttonActions = <Widget>[];
|
||||
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<ChipSetAction>(
|
||||
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 = <PopupMenuEntry<ChipSetAction>>[
|
||||
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<ChipSetAction> _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<Selection<FilterGridItem<T>>>().isSelecting) {
|
||||
_browseToSelectAnimation.forward();
|
||||
|
@ -243,6 +205,12 @@ class _FilterGridAppBarState<T extends CollectionFilter> extends State<FilterGri
|
|||
}
|
||||
}
|
||||
|
||||
void _onActionSelected(ChipSetAction action) {
|
||||
final selection = context.read<Selection<FilterGridItem<T>>>();
|
||||
final selectedFilters = selection.selectedItems.map((v) => v.filter).toSet();
|
||||
actionDelegate.onActionSelected(context, selectedFilters, action);
|
||||
}
|
||||
|
||||
void _goToSearch() {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
|
|
@ -15,8 +15,8 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
|||
final CollectionSource source;
|
||||
final String title;
|
||||
final ChipSortFactor sortFactor;
|
||||
final bool groupable, showHeaders;
|
||||
final ChipSetActionDelegate actionDelegate;
|
||||
final bool showHeaders;
|
||||
final ChipSetActionDelegate<T> actionDelegate;
|
||||
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
||||
final Set<T>? newFilters;
|
||||
final Widget Function() emptyBuilder;
|
||||
|
@ -26,7 +26,6 @@ class FilterNavigationPage<T extends CollectionFilter> 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<T extends CollectionFilter> extends StatelessWidget {
|
|||
source: source,
|
||||
title: title,
|
||||
actionDelegate: actionDelegate,
|
||||
groupable: groupable,
|
||||
isEmpty: filterSections.isEmpty,
|
||||
),
|
||||
sections: filterSections,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue