diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index fa8d6f281..fe58da221 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -627,6 +627,13 @@ "settingsThumbnailShowVideoDuration": "Show video duration", "@settingsThumbnailShowVideoDuration": {}, + "settingsCollectionSelectionQuickActionsTile": "Quick actions for item selection", + "@settingsCollectionSelectionQuickActionsTile": {}, + "settingsCollectionSelectionQuickActionEditorTitle": "Quick Actions", + "@settingsCollectionSelectionQuickActionEditorTitle": {}, + "settingsCollectionSelectionQuickActionEditorBanner": "Touch and hold to move buttons and select which actions are displayed when selecting items.", + "@settingsCollectionSelectionQuickActionEditorBanner": {}, + "settingsSectionViewer": "Viewer", "@settingsSectionViewer": {}, "settingsImageBackground": "Image background", @@ -671,9 +678,9 @@ "@settingsVideoLoopModeTile": {}, "settingsVideoLoopModeTitle": "Loop Mode", "@settingsVideoLoopModeTitle": {}, - "settingsVideoQuickActionsTile": "Quick video actions", + "settingsVideoQuickActionsTile": "Quick actions for videos", "@settingsVideoQuickActionsTile": {}, - "settingsVideoQuickActionEditorTitle": "Quick Video Actions", + "settingsVideoQuickActionEditorTitle": "Quick Actions", "@settingsVideoQuickActionEditorTitle": {}, "settingsSubtitleThemeTile": "Subtitles", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index c055f731d..c4157a173 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -299,6 +299,10 @@ "settingsThumbnailShowRawIcon": "Raw 아이콘 표시", "settingsThumbnailShowVideoDuration": "동영상 길이 표시", + "settingsCollectionSelectionQuickActionsTile": "항목 선택의 빠른 작업", + "settingsCollectionSelectionQuickActionEditorTitle": "빠른 작업", + "settingsCollectionSelectionQuickActionEditorBanner": "버튼을 길게 누른 후 이동하여 항목 선택할 때 표시될 버튼을 선택하세요.", + "settingsSectionViewer": "뷰어", "settingsImageBackground": "사진 배경", "settingsViewerShowMinimap": "미니맵 표시", @@ -322,8 +326,8 @@ "settingsVideoEnableAutoPlay": "자동 재생", "settingsVideoLoopModeTile": "반복 모드", "settingsVideoLoopModeTitle": "반복 모드", - "settingsVideoQuickActionsTile": "빠른 동영상 작업", - "settingsVideoQuickActionEditorTitle": "빠른 동영상 작업", + "settingsVideoQuickActionsTile": "동영상의 빠른 작업", + "settingsVideoQuickActionEditorTitle": "빠른 작업", "settingsSubtitleThemeTile": "자막", "settingsSubtitleThemeTitle": "자막", diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart index ceb1b528e..5d4d580e6 100644 --- a/lib/model/actions/entry_actions.dart +++ b/lib/model/actions/entry_actions.dart @@ -32,11 +32,6 @@ enum EntryAction { } class EntryActions { - static const selection = [ - EntryAction.share, - EntryAction.delete, - ]; - static const inApp = [ EntryAction.info, EntryAction.toggleFavourite, diff --git a/lib/model/actions/entry_set_actions.dart b/lib/model/actions/entry_set_actions.dart index aa8cb1810..732c55ad6 100644 --- a/lib/model/actions/entry_set_actions.dart +++ b/lib/model/actions/entry_set_actions.dart @@ -15,11 +15,25 @@ enum EntrySetAction { map, stats, // entry selection + share, + delete, copy, move, refreshMetadata, } +class EntrySetActions { + static const selection = [ + EntrySetAction.share, + EntrySetAction.delete, + EntrySetAction.copy, + EntrySetAction.move, + EntrySetAction.refreshMetadata, + EntrySetAction.map, + EntrySetAction.stats, + ]; +} + extension ExtraEntrySetAction on EntrySetAction { String getText(BuildContext context) { switch (this) { @@ -43,6 +57,10 @@ extension ExtraEntrySetAction on EntrySetAction { case EntrySetAction.stats: return context.l10n.menuActionStats; // entry selection + case EntrySetAction.share: + return context.l10n.entryActionShare; + case EntrySetAction.delete: + return context.l10n.entryActionDelete; case EntrySetAction.copy: return context.l10n.collectionActionCopy; case EntrySetAction.move: @@ -78,6 +96,10 @@ extension ExtraEntrySetAction on EntrySetAction { case EntrySetAction.stats: return AIcons.stats; // entry selection + case EntrySetAction.share: + return AIcons.share; + case EntrySetAction.delete: + return AIcons.delete; case EntrySetAction.copy: return AIcons.copy; case EntrySetAction.move: diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart new file mode 100644 index 000000000..e7c44407e --- /dev/null +++ b/lib/model/settings/defaults.dart @@ -0,0 +1,41 @@ +import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry_set_actions.dart'; +import 'package:aves/model/actions/video_actions.dart'; +import 'package:aves/model/filters/favourite.dart'; +import 'package:aves/model/filters/mime.dart'; +import 'package:aves/widgets/filter_grids/albums_page.dart'; +import 'package:aves/widgets/filter_grids/countries_page.dart'; +import 'package:aves/widgets/filter_grids/tags_page.dart'; + +class SettingsDefaults { + // drawer + static final drawerTypeBookmarks = [ + null, + MimeFilter.video, + FavouriteFilter.instance, + ]; + static final drawerPageBookmarks = [ + AlbumListPage.routeName, + CountryListPage.routeName, + TagListPage.routeName, + ]; + + // collection + static const collectionSelectionQuickActions = [ + EntrySetAction.share, + EntrySetAction.delete, + ]; + + // viewer + static const viewerQuickActions = [ + EntryAction.toggleFavourite, + EntryAction.share, + EntryAction.rotateScreen, + ]; + + // video + static const videoQuickActions = [ + VideoAction.replay10, + VideoAction.togglePlay, + ]; +} diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index e2be8896c..76ec17bee 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -2,10 +2,10 @@ import 'dart:convert'; import 'dart:math'; import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry_set_actions.dart'; import 'package:aves/model/actions/video_actions.dart'; -import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/map_style.dart'; import 'package:aves/model/settings/screen_on.dart'; @@ -13,9 +13,6 @@ import 'package:aves/model/source/enums.dart'; import 'package:aves/services/device_service.dart'; import 'package:aves/services/services.dart'; import 'package:aves/utils/pedantic.dart'; -import 'package:aves/widgets/filter_grids/albums_page.dart'; -import 'package:aves/widgets/filter_grids/countries_page.dart'; -import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:collection/collection.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; @@ -59,6 +56,7 @@ class Settings extends ChangeNotifier { // collection static const collectionGroupFactorKey = 'collection_group_factor'; static const collectionSortFactorKey = 'collection_sort_factor'; + static const collectionSelectionQuickActionsKey = 'collection_selection_quick_actions'; static const showThumbnailLocationKey = 'show_thumbnail_location'; static const showThumbnailRawKey = 'show_thumbnail_raw'; static const showThumbnailVideoDurationKey = 'show_thumbnail_video_duration'; @@ -108,27 +106,6 @@ class Settings extends ChangeNotifier { // version static const lastVersionCheckDateKey = 'last_version_check_date'; - // defaults - static final drawerTypeBookmarksDefault = [ - null, - MimeFilter.video, - FavouriteFilter.instance, - ]; - static final drawerPageBookmarksDefault = [ - AlbumListPage.routeName, - CountryListPage.routeName, - TagListPage.routeName, - ]; - static const viewerQuickActionsDefault = [ - EntryAction.toggleFavourite, - EntryAction.share, - EntryAction.rotateScreen, - ]; - static const videoQuickActionsDefault = [ - VideoAction.replay10, - VideoAction.togglePlay, - ]; - Future init() async { _prefs = await SharedPreferences.getInstance(); _isRotationLocked = await windowService.isRotationLocked(); @@ -235,7 +212,7 @@ class Settings extends ChangeNotifier { if (v.isEmpty) return null; return CollectionFilter.fromJson(v); }).toList() ?? - drawerTypeBookmarksDefault; + SettingsDefaults.drawerTypeBookmarks; set drawerTypeBookmarks(List newValue) => setAndNotify(drawerTypeBookmarksKey, newValue.map((filter) => filter?.toJson() ?? '').toList()); @@ -243,7 +220,7 @@ class Settings extends ChangeNotifier { set drawerAlbumBookmarks(List? newValue) => setAndNotify(drawerAlbumBookmarksKey, newValue); - List get drawerPageBookmarks => _prefs!.getStringList(drawerPageBookmarksKey) ?? drawerPageBookmarksDefault; + List get drawerPageBookmarks => _prefs!.getStringList(drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks; set drawerPageBookmarks(List newValue) => setAndNotify(drawerPageBookmarksKey, newValue); @@ -257,6 +234,10 @@ class Settings extends ChangeNotifier { set collectionSortFactor(EntrySortFactor newValue) => setAndNotify(collectionSortFactorKey, newValue.toString()); + List get collectionSelectionQuickActions => getEnumListOrDefault(collectionSelectionQuickActionsKey, SettingsDefaults.collectionSelectionQuickActions, EntrySetAction.values); + + set collectionSelectionQuickActions(List newValue) => setAndNotify(collectionSelectionQuickActionsKey, newValue.map((v) => v.toString()).toList()); + bool get showThumbnailLocation => getBoolOrDefault(showThumbnailLocationKey, true); set showThumbnailLocation(bool newValue) => setAndNotify(showThumbnailLocationKey, newValue); @@ -297,7 +278,7 @@ class Settings extends ChangeNotifier { // viewer - List get viewerQuickActions => getEnumListOrDefault(viewerQuickActionsKey, viewerQuickActionsDefault, EntryAction.values); + List get viewerQuickActions => getEnumListOrDefault(viewerQuickActionsKey, SettingsDefaults.viewerQuickActions, EntryAction.values); set viewerQuickActions(List newValue) => setAndNotify(viewerQuickActionsKey, newValue.map((v) => v.toString()).toList()); @@ -323,7 +304,7 @@ class Settings extends ChangeNotifier { // video - List get videoQuickActions => getEnumListOrDefault(videoQuickActionsKey, videoQuickActionsDefault, VideoAction.values); + List get videoQuickActions => getEnumListOrDefault(videoQuickActionsKey, SettingsDefaults.videoQuickActions, VideoAction.values); set videoQuickActions(List newValue) => setAndNotify(videoQuickActionsKey, newValue.map((v) => v.toString()).toList()); @@ -556,6 +537,7 @@ class Settings extends ChangeNotifier { case drawerPageBookmarksKey: case pinnedFiltersKey: case hiddenFiltersKey: + case collectionSelectionQuickActionsKey: case viewerQuickActionsKey: case videoQuickActionsKey: if (value is List) { diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 7fcb9a5eb..4ce99fc00 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_set_actions.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; @@ -168,6 +167,7 @@ class _CollectionAppBarState extends State with SingleTickerPr List _buildActions(bool isSelecting) { final appMode = context.watch>().value; + final selectionQuickActions = settings.collectionSelectionQuickActions; return [ if (!isSelecting && appMode.canSearch) CollectionSearchButton( @@ -175,11 +175,11 @@ class _CollectionAppBarState extends State with SingleTickerPr parentCollection: collection, ), if (isSelecting) - ...EntryActions.selection.map((action) => Selector, bool>( + ...selectionQuickActions.map((action) => Selector, bool>( selector: (context, selection) => selection.selectedItems.isEmpty, builder: (context, isEmpty, child) => IconButton( - icon: action.getIcon() ?? const SizedBox(), - onPressed: isEmpty ? null : () => _actionDelegate.onEntryActionSelected(context, action), + icon: action.getIcon(), + onPressed: isEmpty ? null : () => _onCollectionActionSelected(action), tooltip: action.getText(context), ), )), @@ -219,16 +219,12 @@ class _CollectionAppBarState extends State with SingleTickerPr enabled: hasItems, ), const PopupMenuDivider(), - if (isSelecting) + if (isSelecting) ...EntrySetActions.selection.where((v) => !selectionQuickActions.contains(v)).map((v) => _toMenuItem(v, enabled: hasSelection)), + if (!isSelecting) ...[ - EntrySetAction.copy, - EntrySetAction.move, - EntrySetAction.refreshMetadata, - ].map((v) => _toMenuItem(v, enabled: hasSelection)), - ...[ - EntrySetAction.map, - EntrySetAction.stats, - ].map((v) => _toMenuItem(v, enabled: otherViewEnabled)), + EntrySetAction.map, + EntrySetAction.stats, + ].map((v) => _toMenuItem(v, enabled: otherViewEnabled)), if (!isSelecting && canAddShortcuts) ...[ const PopupMenuDivider(), _toMenuItem(EntrySetAction.addShortcut), @@ -290,12 +286,14 @@ class _CollectionAppBarState extends State with SingleTickerPr Future _onCollectionActionSelected(EntrySetAction action) async { switch (action) { + case EntrySetAction.share: + case EntrySetAction.delete: case EntrySetAction.copy: case EntrySetAction.move: case EntrySetAction.refreshMetadata: case EntrySetAction.map: case EntrySetAction.stats: - _actionDelegate.onCollectionActionSelected(context, action); + _actionDelegate.onActionSelected(context, action); break; case EntrySetAction.select: context.read>().select(); diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 4a09f2684..8e6b636ad 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_set_actions.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/entry.dart'; @@ -30,21 +29,14 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { - void onEntryActionSelected(BuildContext context, EntryAction action) { + void onActionSelected(BuildContext context, EntrySetAction action) { switch (action) { - case EntryAction.delete: - _showDeleteDialog(context); - break; - case EntryAction.share: + case EntrySetAction.share: _share(context); break; - default: + case EntrySetAction.delete: + _showDeleteDialog(context); break; - } - } - - void onCollectionActionSelected(BuildContext context, EntrySetAction action) { - switch (action) { case EntrySetAction.copy: _moveSelection(context, moveType: MoveType.copy); break; diff --git a/lib/widgets/debug/settings.dart b/lib/widgets/debug/settings.dart index c54138a13..b21cc3212 100644 --- a/lib/widgets/debug/settings.dart +++ b/lib/widgets/debug/settings.dart @@ -52,6 +52,7 @@ class DebugSettingsSection extends StatelessWidget { 'tileExtent - Countries': '${settings.getTileExtent(CountryListPage.routeName)}', 'tileExtent - Tags': '${settings.getTileExtent(TagListPage.routeName)}', 'infoMapZoom': '${settings.infoMapZoom}', + 'collectionSelectionQuickActions': '${settings.collectionSelectionQuickActions}', 'viewerQuickActions': '${settings.viewerQuickActions}', 'videoQuickActions': '${settings.videoQuickActions}', 'drawerTypeBookmarks': toMultiline(settings.drawerTypeBookmarks), diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index e226f5773..24528b4bd 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -14,7 +14,7 @@ import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/settings/language/language.dart'; import 'package:aves/widgets/settings/navigation/navigation.dart'; import 'package:aves/widgets/settings/privacy/privacy.dart'; -import 'package:aves/widgets/settings/thumbnails.dart'; +import 'package:aves/widgets/settings/thumbnails/thumbnails.dart'; import 'package:aves/widgets/settings/video/video.dart'; import 'package:aves/widgets/settings/viewer/viewer.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/settings/thumbnails/selection_actions_editor.dart b/lib/widgets/settings/thumbnails/selection_actions_editor.dart new file mode 100644 index 000000000..218736adc --- /dev/null +++ b/lib/widgets/settings/thumbnails/selection_actions_editor.dart @@ -0,0 +1,44 @@ +import 'package:aves/model/actions/entry_set_actions.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart'; +import 'package:flutter/material.dart'; + +class SelectionActionsTile extends StatelessWidget { + const SelectionActionsTile({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(context.l10n.settingsCollectionSelectionQuickActionsTile), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: SelectionActionEditorPage.routeName), + builder: (context) => const SelectionActionEditorPage(), + ), + ); + }, + ); + } +} + +class SelectionActionEditorPage extends StatelessWidget { + static const routeName = '/settings/collection_selection_actions'; + + const SelectionActionEditorPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return QuickActionEditorPage( + title: context.l10n.settingsCollectionSelectionQuickActionEditorTitle, + bannerText: context.l10n.settingsCollectionSelectionQuickActionEditorBanner, + allAvailableActions: EntrySetActions.selection, + actionIcon: (action) => action.getIcon(), + actionText: (context, action) => action.getText(context), + load: () => settings.collectionSelectionQuickActions.toList(), + save: (actions) => settings.collectionSelectionQuickActions = actions, + ); + } +} diff --git a/lib/widgets/settings/thumbnails.dart b/lib/widgets/settings/thumbnails/thumbnails.dart similarity index 96% rename from lib/widgets/settings/thumbnails.dart rename to lib/widgets/settings/thumbnails/thumbnails.dart index 3735c1bb5..0670c13ac 100644 --- a/lib/widgets/settings/thumbnails.dart +++ b/lib/widgets/settings/thumbnails/thumbnails.dart @@ -5,6 +5,7 @@ import 'package:aves/utils/color_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart'; +import 'package:aves/widgets/settings/thumbnails/selection_actions_editor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -34,6 +35,7 @@ class ThumbnailsSection extends StatelessWidget { expandedNotifier: expandedNotifier, showHighlight: false, children: [ + const SelectionActionsTile(), SwitchListTile( value: currentShowThumbnailLocation, onChanged: (v) => settings.showThumbnailLocation = v,