diff --git a/lib/model/settings.dart b/lib/model/settings.dart index 42bbf0e8c..8a1da8cbb 100644 --- a/lib/model/settings.dart +++ b/lib/model/settings.dart @@ -1,4 +1,5 @@ import 'package:aves/model/collection_lens.dart'; +import 'package:aves/widgets/fullscreen/fullscreen_actions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -19,6 +20,7 @@ class Settings { static const collectionSortFactorKey = 'collection_sort_factor'; static const infoMapZoomKey = 'info_map_zoom'; static const catalogTimeZoneKey = 'catalog_time_zone'; + static const mostRecentFullscreenActionsKey = 'most_recent_fullscreen_actions'; Future init() async { prefs = await SharedPreferences.getInstance(); @@ -60,6 +62,17 @@ class Settings { set collectionSortFactor(SortFactor newValue) => setAndNotify(collectionSortFactorKey, newValue.toString()); + List get mostRecentFullscreenActions => getEnumListOrDefault( + mostRecentFullscreenActionsKey, + [ + FullscreenAction.toggleFavourite, + FullscreenAction.share, + FullscreenAction.delete, + ], + FullscreenAction.values); + + set mostRecentFullscreenActions(List newValue) => setAndNotify(mostRecentFullscreenActionsKey, newValue.map((v) => v.toString()).toList()); + // convenience methods bool getBoolOrDefault(String key, bool defaultValue) => prefs.getKeys().contains(key) ? prefs.getBool(key) : defaultValue; @@ -74,6 +87,10 @@ class Settings { return defaultValue; } + List getEnumListOrDefault(String key, List defaultValue, Iterable values) { + return prefs.getStringList(key)?.map((s) => values.firstWhere((el) => el.toString() == s, orElse: () => null))?.where((el) => el != null)?.toList() ?? defaultValue; + } + void setAndNotify(String key, dynamic newValue) { var oldValue = prefs.get(key); if (newValue == null) { @@ -81,6 +98,9 @@ class Settings { } else if (newValue is String) { oldValue = prefs.getString(key); prefs.setString(key, newValue); + } else if (newValue is List) { + oldValue = prefs.getStringList(key); + prefs.setStringList(key, newValue); } else if (newValue is int) { oldValue = prefs.getInt(key); prefs.setInt(key, newValue); diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart index fb122e52c..11c4c109d 100644 --- a/lib/widgets/debug_page.dart +++ b/lib/widgets/debug_page.dart @@ -51,6 +51,7 @@ class DebugPageState extends State { Text('collectionGroupFactor: ${settings.collectionGroupFactor}'), Text('collectionSortFactor: ${settings.collectionSortFactor}'), Text('infoMapZoom: ${settings.infoMapZoom}'), + Text('mostRecentFullscreenActions: ${settings.mostRecentFullscreenActions}'), const Divider(), Text('Entries: ${entries.length}'), Text('Catalogued: ${catalogued.length}'), diff --git a/lib/widgets/fullscreen/fullscreen_action_delegate.dart b/lib/widgets/fullscreen/fullscreen_action_delegate.dart index 6484710b5..1b833dd0d 100644 --- a/lib/widgets/fullscreen/fullscreen_action_delegate.dart +++ b/lib/widgets/fullscreen/fullscreen_action_delegate.dart @@ -4,14 +4,13 @@ import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/android_app_service.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; +import 'package:aves/widgets/fullscreen/fullscreen_actions.dart'; import 'package:flushbar/flushbar.dart'; import 'package:flutter/material.dart'; import 'package:pdf/widgets.dart' as pdf; import 'package:pedantic/pedantic.dart'; import 'package:printing/printing.dart'; -enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share, toggleFavourite } - class FullscreenActionDelegate { final CollectionLens collection; final VoidCallback showInfo; diff --git a/lib/widgets/fullscreen/fullscreen_actions.dart b/lib/widgets/fullscreen/fullscreen_actions.dart new file mode 100644 index 000000000..9f888b8e7 --- /dev/null +++ b/lib/widgets/fullscreen/fullscreen_actions.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:outline_material_icons/outline_material_icons.dart'; +import 'package:tuple/tuple.dart'; + +enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share, toggleFavourite } + +class FullscreenActions { + static const inApp = [ + FullscreenAction.info, + FullscreenAction.toggleFavourite, + FullscreenAction.share, + FullscreenAction.delete, + FullscreenAction.rename, + FullscreenAction.rotateCCW, + FullscreenAction.rotateCW, + FullscreenAction.print, + ]; + + static const externalApp = [ + FullscreenAction.edit, + FullscreenAction.open, + FullscreenAction.setAs, + FullscreenAction.openMap, + ]; + + static Tuple2 getTextIcon(FullscreenAction action) { + switch (action) { + // in app actions + case FullscreenAction.delete: + return const Tuple2('Delete', OMIcons.delete); + case FullscreenAction.info: + return const Tuple2('Info', OMIcons.info); + case FullscreenAction.rename: + return const Tuple2('Rename', OMIcons.title); + case FullscreenAction.rotateCCW: + return const Tuple2('Rotate left', OMIcons.rotateLeft); + case FullscreenAction.rotateCW: + return const Tuple2('Rotate right', OMIcons.rotateRight); + case FullscreenAction.print: + return const Tuple2('Print', OMIcons.print); + case FullscreenAction.share: + return const Tuple2('Share', OMIcons.share); + // external app actions + case FullscreenAction.edit: + return const Tuple2('Edit with…', null); + case FullscreenAction.open: + return const Tuple2('Open with…', null); + case FullscreenAction.setAs: + return const Tuple2('Set as…', null); + case FullscreenAction.openMap: + return const Tuple2('Show on map…', null); + } + return null; + } +} diff --git a/lib/widgets/fullscreen/overlay/top.dart b/lib/widgets/fullscreen/overlay/top.dart index d549af688..73cea89c2 100644 --- a/lib/widgets/fullscreen/overlay/top.dart +++ b/lib/widgets/fullscreen/overlay/top.dart @@ -1,9 +1,10 @@ import 'dart:math'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/model/settings.dart'; import 'package:aves/widgets/common/fx/sweeper.dart'; import 'package:aves/widgets/common/menu_row.dart'; -import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart'; +import 'package:aves/widgets/fullscreen/fullscreen_actions.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -22,6 +23,12 @@ class FullscreenTopOverlay extends StatelessWidget { static const double padding = 8; + static const int landscapeActionCount = 3; + + static const int portraitActionCount = 2; + + static const List possibleOverlayActions = FullscreenActions.inApp; + const FullscreenTopOverlay({ Key key, @required this.entries, @@ -42,35 +49,15 @@ class FullscreenTopOverlay extends StatelessWidget { child: Selector( selector: (c, mq) => mq.orientation, builder: (c, orientation, child) { - final targetCount = orientation == Orientation.landscape ? 3 : 2; + final targetCount = orientation == Orientation.landscape ? landscapeActionCount : portraitActionCount; return LayoutBuilder( builder: (context, constraints) { final availableCount = (constraints.maxWidth / (kMinInteractiveDimension + padding)).floor() - 2; final recentActionCount = min(targetCount, availableCount); - final recentActions = [ - FullscreenAction.toggleFavourite, - FullscreenAction.share, - FullscreenAction.delete, - FullscreenAction.info, - FullscreenAction.rename, - ].where(_canDo).take(recentActionCount); - final inAppActions = [ - FullscreenAction.info, - FullscreenAction.toggleFavourite, - FullscreenAction.share, - FullscreenAction.delete, - FullscreenAction.rename, - FullscreenAction.rotateCCW, - FullscreenAction.rotateCW, - FullscreenAction.print, - ].where((action) => !recentActions.contains(action)).where(_canDo); - final externalAppActions = [ - FullscreenAction.edit, - FullscreenAction.open, - FullscreenAction.setAs, - FullscreenAction.openMap, - ].where(_canDo); + final recentActions = settings.mostRecentFullscreenActions.where(_canDo).take(recentActionCount); + final inAppActions = FullscreenActions.inApp.where((action) => !recentActions.contains(action)).where(_canDo); + final externalAppActions = FullscreenActions.externalApp.where(_canDo); return Row( children: [ @@ -88,7 +75,12 @@ class FullscreenTopOverlay extends StatelessWidget { const PopupMenuDivider(), ...externalAppActions.map(_buildPopupMenuItem), ], - onSelected: onActionSelected, + onSelected: (action) { + if (possibleOverlayActions.contains(action)) { + settings.mostRecentFullscreenActions = [action, ...settings.mostRecentFullscreenActions].take(landscapeActionCount).toList(); + } + onActionSelected?.call(action); + }, ), ), ], @@ -148,37 +140,20 @@ class FullscreenTopOverlay extends StatelessWidget { ), ); break; - case FullscreenAction.share: - child = IconButton( - icon: const Icon(OMIcons.share), - onPressed: onPressed, - tooltip: 'Share', - ); - break; - case FullscreenAction.delete: - child = IconButton( - icon: const Icon(OMIcons.delete), - onPressed: onPressed, - tooltip: 'Delete', - ); - break; case FullscreenAction.info: - child = IconButton( - icon: const Icon(OMIcons.info), - onPressed: onPressed, - tooltip: 'Info', - ); - break; + case FullscreenAction.share: + case FullscreenAction.delete: case FullscreenAction.rename: - child = IconButton( - icon: const Icon(OMIcons.title), - onPressed: onPressed, - tooltip: 'Rename', - ); - break; case FullscreenAction.rotateCCW: case FullscreenAction.rotateCW: case FullscreenAction.print: + final textIcon = FullscreenActions.getTextIcon(action); + child = IconButton( + icon: Icon(textIcon.item2), + onPressed: onPressed, + tooltip: textIcon.item1, + ); + break; case FullscreenAction.openMap: case FullscreenAction.open: case FullscreenAction.edit: @@ -200,9 +175,6 @@ class FullscreenTopOverlay extends StatelessWidget { Widget child; switch (action) { // in app actions - case FullscreenAction.info: - child = const MenuRow(text: 'Info', icon: OMIcons.info); - break; case FullscreenAction.toggleFavourite: child = entry.isFavouriteNotifier.value ? const MenuRow( @@ -214,36 +186,23 @@ class FullscreenTopOverlay extends StatelessWidget { icon: OMIcons.favoriteBorder, ); break; + case FullscreenAction.info: case FullscreenAction.share: - child = const MenuRow(text: 'Share', icon: OMIcons.share); - break; case FullscreenAction.delete: - child = const MenuRow(text: 'Delete', icon: OMIcons.delete); - break; case FullscreenAction.rename: - child = const MenuRow(text: 'Rename', icon: OMIcons.title); - break; case FullscreenAction.rotateCCW: - child = const MenuRow(text: 'Rotate left', icon: OMIcons.rotateLeft); - break; case FullscreenAction.rotateCW: - child = const MenuRow(text: 'Rotate right', icon: OMIcons.rotateRight); - break; case FullscreenAction.print: - child = const MenuRow(text: 'Print', icon: OMIcons.print); + final textIcon = FullscreenActions.getTextIcon(action); + child = MenuRow(text: textIcon.item1, icon: textIcon.item2); break; // external app actions case FullscreenAction.edit: - child = const Text('Edit with…'); - break; case FullscreenAction.open: - child = const Text('Open with…'); - break; case FullscreenAction.setAs: - child = const Text('Set as…'); - break; case FullscreenAction.openMap: - child = const Text('Show on map…'); + final textIcon = FullscreenActions.getTextIcon(action); + child = Text(textIcon.item1); break; } return PopupMenuItem(