diff --git a/CHANGELOG.md b/CHANGELOG.md index b1af20889..4c16d988d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ### Added +- Explorer: set custom path as home +- Explorer: create shortcut to custom path - predictive back support (inter-app) ### Changed diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index 00bb5fcad..62b8fb8b1 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -294,11 +294,9 @@ open class MainActivity : FlutterActivity() { if (intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false)) { fields[INTENT_DATA_KEY_SAFE_MODE] = true } - intent.getStringExtra(EXTRA_KEY_PAGE)?.let { page -> - val filters = extractFiltersFromIntent(intent) - fields[INTENT_DATA_KEY_PAGE] = page - fields[INTENT_DATA_KEY_FILTERS] = filters - } + fields[INTENT_DATA_KEY_PAGE] = intent.getStringExtra(EXTRA_KEY_PAGE) + fields[INTENT_DATA_KEY_FILTERS] = extractFiltersFromIntent(intent) + fields[INTENT_DATA_KEY_EXPLORER_PATH] = intent.getStringExtra(EXTRA_KEY_EXPLORER_PATH) return fields } @@ -527,6 +525,7 @@ open class MainActivity : FlutterActivity() { const val INTENT_DATA_KEY_ACTION = "action" const val INTENT_DATA_KEY_ALLOW_MULTIPLE = "allowMultiple" const val INTENT_DATA_KEY_BRIGHTNESS = "brightness" + const val INTENT_DATA_KEY_EXPLORER_PATH = "explorerPath" const val INTENT_DATA_KEY_FILTERS = "filters" const val INTENT_DATA_KEY_MIME_TYPE = "mimeType" const val INTENT_DATA_KEY_PAGE = "page" @@ -537,6 +536,7 @@ open class MainActivity : FlutterActivity() { const val INTENT_DATA_KEY_WIDGET_ID = "widgetId" const val EXTRA_KEY_PAGE = "page" + const val EXTRA_KEY_EXPLORER_PATH = "explorerPath" const val EXTRA_KEY_FILTERS_ARRAY = "filters" const val EXTRA_KEY_FILTERS_STRING = "filtersString" const val EXTRA_KEY_SAFE_MODE = "safeMode" diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt index 350c71670..1abc8cb32 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt @@ -19,6 +19,7 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.request.RequestOptions import deckers.thibault.aves.MainActivity +import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_EXPLORER_PATH import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_ARRAY import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_STRING import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_PAGE @@ -351,8 +352,9 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { val label = call.argument("label") val iconBytes = call.argument("iconBytes") val filters = call.argument>("filters") + val explorerPath = call.argument("explorerPath") val uri = call.argument("uri")?.let { Uri.parse(it) } - if (label == null || (filters == null && uri == null)) { + if (label == null) { result.error("pin-args", "missing arguments", null) return } @@ -380,7 +382,6 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { } val intent = when { - uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java) filters != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java) .putExtra(EXTRA_KEY_PAGE, "/collection") .putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray()) @@ -388,6 +389,11 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { // so we use a joined `String` as fallback .putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR)) + explorerPath != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java) + .putExtra(EXTRA_KEY_PAGE, "/explorer") + .putExtra(EXTRA_KEY_EXPLORER_PATH, explorerPath) + + uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java) else -> { result.error("pin-intent", "failed to build intent", null) return diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ef0394040..f7dbac54c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -771,6 +771,9 @@ "binPageTitle": "Recycle Bin", "explorerPageTitle": "Explorer", + "explorerActionSelectStorageVolume": "Select storage", + + "selectStorageVolumeDialogTitle": "Select Storage", "searchCollectionFieldHint": "Search collection", "searchRecentSectionTitle": "Recent", @@ -804,7 +807,7 @@ "settingsNavigationSectionTitle": "Navigation", "settingsHomeTile": "Home", "settingsHomeDialogTitle": "Home", - "setHomeCustomCollection": "Custom collection", + "setHomeCustom": "Custom", "settingsShowBottomNavigationBar": "Show bottom navigation bar", "settingsKeepScreenOnTile": "Keep screen on", "settingsKeepScreenOnDialogTitle": "Keep Screen On", diff --git a/lib/model/settings/modules/navigation.dart b/lib/model/settings/modules/navigation.dart index c11c97968..a2c6969a5 100644 --- a/lib/model/settings/modules/navigation.dart +++ b/lib/model/settings/modules/navigation.dart @@ -14,11 +14,19 @@ mixin NavigationSettings on SettingsAccess { HomePageSetting get homePage => getEnumOrDefault(SettingKeys.homePageKey, SettingsDefaults.homePage, HomePageSetting.values); - set homePage(HomePageSetting newValue) => set(SettingKeys.homePageKey, newValue.toString()); - Set get homeCustomCollection => (getStringList(SettingKeys.homeCustomCollectionKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); - set homeCustomCollection(Set newValue) => set(SettingKeys.homeCustomCollectionKey, newValue.map((filter) => filter.toJson()).toList()); + String? get homeCustomExplorerPath => getString(SettingKeys.homeCustomExplorerPathKey); + + void setHome( + HomePageSetting homePage, { + Set customCollection = const {}, + String? customExplorerPath, + }) { + set(SettingKeys.homePageKey, homePage.toString()); + set(SettingKeys.homeCustomCollectionKey, customCollection.map((filter) => filter.toJson()).toList()); + set(SettingKeys.homeCustomExplorerPathKey, customExplorerPath); + } bool get enableBottomNavigationBar => getBool(SettingKeys.enableBottomNavigationBarKey) ?? SettingsDefaults.enableBottomNavigationBar; diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index e5a046b03..67302c862 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -440,6 +440,7 @@ class Settings with ChangeNotifier, SettingsAccess, AppSettings, DisplaySettings case SettingKeys.maxBrightnessKey: case SettingKeys.keepScreenOnKey: case SettingKeys.homePageKey: + case SettingKeys.homeCustomExplorerPathKey: case SettingKeys.collectionGroupFactorKey: case SettingKeys.collectionSortFactorKey: case SettingKeys.thumbnailLocationIconKey: diff --git a/lib/services/app_service.dart b/lib/services/app_service.dart index 25295e367..774dacedc 100644 --- a/lib/services/app_service.dart +++ b/lib/services/app_service.dart @@ -30,7 +30,7 @@ abstract class AppService { Future shareSingle(String uri, String mimeType); - Future pinToHomeScreen(String label, AvesEntry? coverEntry, {Set? filters, String? uri}); + Future pinToHomeScreen(String label, AvesEntry? coverEntry, {Set? filters, String? explorerPath, String? uri}); } class PlatformAppService implements AppService { @@ -203,7 +203,7 @@ class PlatformAppService implements AppService { // app shortcuts @override - Future pinToHomeScreen(String label, AvesEntry? coverEntry, {Set? filters, String? uri}) async { + Future pinToHomeScreen(String label, AvesEntry? coverEntry, {Set? filters, String? explorerPath, String? uri}) async { Uint8List? iconBytes; if (coverEntry != null) { final size = coverEntry.isVideo ? 0.0 : 256.0; @@ -222,6 +222,7 @@ class PlatformAppService implements AppService { 'label': label, 'iconBytes': iconBytes, 'filters': filters?.map((filter) => filter.toJson()).toList(), + 'explorerPath': explorerPath, 'uri': uri, }); } on PlatformException catch (e, stack) { diff --git a/lib/view/src/actions/explorer.dart b/lib/view/src/actions/explorer.dart new file mode 100644 index 000000000..38fb845c3 --- /dev/null +++ b/lib/view/src/actions/explorer.dart @@ -0,0 +1,23 @@ +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves_model/aves_model.dart'; +import 'package:flutter/material.dart'; + +extension ExtraExplorerActionView on ExplorerAction { + String getText(BuildContext context) { + final l10n = context.l10n; + return switch (this) { + ExplorerAction.addShortcut => l10n.collectionActionAddShortcut, + ExplorerAction.setHome => l10n.collectionActionSetHome, + }; + } + + Widget getIcon() => Icon(_getIconData()); + + IconData _getIconData() { + return switch (this) { + ExplorerAction.addShortcut => AIcons.addShortcut, + ExplorerAction.setHome => AIcons.home, + }; + } +} diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index db0504ad1..11edabc8c 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -753,8 +753,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware } void _setHome(BuildContext context) async { - settings.homeCustomCollection = context.read().filters; - settings.homePage = HomePageSetting.collection; + settings.setHome(HomePageSetting.collection, customCollection: context.read().filters); showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); } } diff --git a/lib/widgets/dialogs/select_storage_dialog.dart b/lib/widgets/dialogs/select_storage_dialog.dart new file mode 100644 index 000000000..fa667c19e --- /dev/null +++ b/lib/widgets/dialogs/select_storage_dialog.dart @@ -0,0 +1,75 @@ +import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/view/view.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:aves_model/aves_model.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; + +class SelectStorageDialog extends StatefulWidget { + static const routeName = '/dialog/select_storage'; + + final StorageVolume? initialVolume; + + const SelectStorageDialog({super.key, this.initialVolume}); + + @override + State createState() => _SelectStorageDialogState(); +} + +class _SelectStorageDialogState extends State { + late Set _allVolumes; + late StorageVolume? _primaryVolume, _selectedVolume; + + @override + void initState() { + super.initState(); + _allVolumes = androidFileUtils.storageVolumes; + _primaryVolume = _allVolumes.firstWhereOrNull((volume) => volume.isPrimary) ?? _allVolumes.firstOrNull; + _selectedVolume = widget.initialVolume ?? _primaryVolume; + } + + @override + Widget build(BuildContext context) { + final byPrimary = groupBy(_allVolumes, (volume) => volume.isPrimary); + int compare(StorageVolume a, StorageVolume b) => compareAsciiUpperCaseNatural(a.path, b.path); + final primaryVolumes = (byPrimary[true] ?? [])..sort(compare); + final otherVolumes = (byPrimary[false] ?? [])..sort(compare); + + return AvesDialog( + title: context.l10n.selectStorageVolumeDialogTitle, + scrollableContent: [ + ...primaryVolumes.map((volume) => _buildVolumeTile(context, volume)), + ...otherVolumes.map((volume) => _buildVolumeTile(context, volume)), + ], + actions: [ + const CancelButton(), + TextButton( + onPressed: () => Navigator.maybeOf(context)?.pop(_selectedVolume), + child: Text(context.l10n.applyButtonLabel), + ), + ], + ); + } + + Widget _buildVolumeTile(BuildContext context, StorageVolume volume) => RadioListTile( + value: volume, + groupValue: _selectedVolume, + onChanged: (volume) { + _selectedVolume = volume!; + setState(() {}); + }, + title: Text( + volume.getDescription(context), + softWrap: false, + overflow: TextOverflow.fade, + maxLines: 1, + ), + subtitle: Text( + volume.path, + softWrap: false, + overflow: TextOverflow.fade, + maxLines: 1, + ), + ); +} diff --git a/lib/widgets/dialogs/selection_dialogs/common.dart b/lib/widgets/dialogs/selection_dialogs/common.dart index 3c80ec6a9..7fa8b4708 100644 --- a/lib/widgets/dialogs/selection_dialogs/common.dart +++ b/lib/widgets/dialogs/selection_dialogs/common.dart @@ -20,4 +20,4 @@ Future showSelectionDialog({ } } -typedef TextBuilder = String Function(T value); +typedef TextBuilder = String? Function(T value); diff --git a/lib/widgets/explorer/app_bar.dart b/lib/widgets/explorer/app_bar.dart index 061b04435..c5a996ee9 100644 --- a/lib/widgets/explorer/app_bar.dart +++ b/lib/widgets/explorer/app_bar.dart @@ -7,6 +7,7 @@ import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/view/src/actions/explorer.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart'; import 'package:aves/widgets/common/app_bar/app_bar_title.dart'; @@ -15,9 +16,12 @@ import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_app_bar.dart'; import 'package:aves/widgets/common/search/route.dart'; +import 'package:aves/widgets/dialogs/select_storage_dialog.dart'; +import 'package:aves/widgets/explorer/explorer_action_delegate.dart'; import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/settings/privacy/file_picker/crumb_line.dart'; import 'package:aves_model/aves_model.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; @@ -108,32 +112,75 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse onPressed: () => _goToSearch(context), tooltip: MaterialLocalizations.of(context).searchFieldLabel, ), - if (_volumes.length > 1) - FontSizeIconTheme( - child: PopupMenuButton( - itemBuilder: (context) { - return _volumes.map((v) { - final selected = widget.directoryNotifier.value.volumePath == v.path; - final icon = v.isRemovable ? AIcons.storageCard : AIcons.storageMain; - return PopupMenuItem( - value: v, - enabled: !selected, - child: MenuRow( - text: v.getDescription(context), - icon: Icon(icon), - ), - ); - }).toList(); - }, - onSelected: (volume) async { - // wait for the popup menu to hide before proceeding with the action - await Future.delayed(animations.popUpAnimationDelay * timeDilation); - widget.goTo(volume.path); - }, - popUpAnimationStyle: animations.popUpAnimationStyle, - ), - ), - ]; + if (_volumes.length > 1) _buildVolumeSelector(context), + PopupMenuButton( + itemBuilder: (context) { + return [ + ExplorerAction.addShortcut, + ExplorerAction.setHome, + ].map((v) { + return PopupMenuItem( + value: v, + child: MenuRow(text: v.getText(context), icon: v.getIcon()), + ); + }).toList(); + }, + onSelected: (action) async { + // wait for the popup menu to hide before proceeding with the action + await Future.delayed(animations.popUpAnimationDelay * timeDilation); + final directory = widget.directoryNotifier.value; + ExplorerActionDelegate(directory: directory).onActionSelected(context, action); + }, + popUpAnimationStyle: animations.popUpAnimationStyle, + ), + ].map((v) => FontSizeIconTheme(child: v)).toList(); + } + + Widget _buildVolumeSelector(BuildContext context) { + if (_volumes.length == 2) { + return ValueListenableBuilder( + valueListenable: widget.directoryNotifier, + builder: (context, directory, child) { + final currentVolume = directory.volumePath; + final otherVolume = _volumes.firstWhere((volume) => volume.path != currentVolume); + final icon = otherVolume.isRemovable ? AIcons.storageCard : AIcons.storageMain; + return IconButton( + icon: Icon(icon), + onPressed: () => widget.goTo(otherVolume.path), + tooltip: otherVolume.getDescription(context), + ); + }, + ); + } else { + return IconButton( + icon: const Icon(AIcons.storageCard), + onPressed: () async { + _volumes.map((v) { + final selected = widget.directoryNotifier.value.volumePath == v.path; + final icon = v.isRemovable ? AIcons.storageCard : AIcons.storageMain; + return PopupMenuItem( + value: v, + enabled: !selected, + child: MenuRow( + text: v.getDescription(context), + icon: Icon(icon), + ), + ); + }).toList(); + final volumePath = widget.directoryNotifier.value.volumePath; + final initialVolume = _volumes.firstWhereOrNull((v) => v.path == volumePath); + final volume = await showDialog( + context: context, + builder: (context) => SelectStorageDialog(initialVolume: initialVolume), + routeSettings: const RouteSettings(name: SelectStorageDialog.routeName), + ); + if (volume != null) { + widget.goTo(volume.path); + } + }, + tooltip: context.l10n.explorerActionSelectStorageVolume, + ); + } } double get appBarContentHeight { diff --git a/lib/widgets/explorer/explorer_action_delegate.dart b/lib/widgets/explorer/explorer_action_delegate.dart new file mode 100644 index 000000000..82d938d52 --- /dev/null +++ b/lib/widgets/explorer/explorer_action_delegate.dart @@ -0,0 +1,85 @@ +import 'package:aves/app_mode.dart'; +import 'package:aves/model/device.dart'; +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/filters/path.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart'; +import 'package:aves_model/aves_model.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class ExplorerActionDelegate with FeedbackMixin { + final VolumeRelativeDirectory directory; + + ExplorerActionDelegate({required this.directory}); + + bool isVisible( + ExplorerAction action, { + required AppMode appMode, + }) { + final isMain = appMode == AppMode.main; + final useTvLayout = settings.useTvLayout; + switch (action) { + case ExplorerAction.addShortcut: + return isMain && device.canPinShortcut; + case ExplorerAction.setHome: + return isMain && !useTvLayout; + } + } + + bool canApply(ExplorerAction action) { + switch (action) { + case ExplorerAction.addShortcut: + case ExplorerAction.setHome: + return true; + } + } + + void onActionSelected(BuildContext context, ExplorerAction action) { + reportService.log('$action'); + switch (action) { + case ExplorerAction.addShortcut: + _addShortcut(context); + case ExplorerAction.setHome: + _setHome(context); + } + } + + Future _addShortcut(BuildContext context) async { + final path = directory.dirPath; + final filter = PathFilter(path); + final defaultName = filter.getLabel(context); + final collection = CollectionLens( + source: context.read(), + filters: {filter}, + ); + + final result = await showDialog<(AvesEntry?, String)>( + context: context, + builder: (context) => AddShortcutDialog( + defaultName: defaultName, + collection: collection, + ), + routeSettings: const RouteSettings(name: AddShortcutDialog.routeName), + ); + if (result == null) return; + + final (coverEntry, name) = result; + if (name.isEmpty) return; + + await appService.pinToHomeScreen(name, coverEntry, explorerPath: path); + if (!device.showPinShortcutFeedback) { + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); + } + } + + void _setHome(BuildContext context) async { + settings.setHome(HomePageSetting.explorer, customExplorerPath: directory.dirPath); + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); + } +} diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 64fc2ae08..225572e23 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -61,11 +61,13 @@ class _HomePageState extends State { int? _widgetId; String? _initialRouteName, _initialSearchQuery; Set? _initialFilters; + String? _initialExplorerPath; List? _secureUris; static const allowedShortcutRoutes = [ - CollectionPage.routeName, AlbumListPage.routeName, + CollectionPage.routeName, + ExplorerPage.routeName, SearchPage.routeName, ]; @@ -92,6 +94,7 @@ class _HomePageState extends State { final safeMode = intentData[IntentDataKeys.safeMode] ?? false; final intentAction = intentData[IntentDataKeys.action]; _initialFilters = null; + _initialExplorerPath = null; _secureUris = null; await androidFileUtils.init(); @@ -186,6 +189,7 @@ class _HomePageState extends State { final extraFilters = intentData[IntentDataKeys.filters]; _initialFilters = extraFilters != null ? (extraFilters as List).cast().map(CollectionFilter.fromJson).whereNotNull().toSet() : null; } + _initialExplorerPath = intentData[IntentDataKeys.explorerPath]; } context.read>().value = appMode; unawaited(reportService.setCustomKey('app_mode', appMode.toString())); @@ -351,7 +355,8 @@ class _HomePageState extends State { case TagListPage.routeName: return buildRoute((context) => const TagListPage()); case ExplorerPage.routeName: - return buildRoute((context) => const ExplorerPage()); + final path = _initialExplorerPath ?? settings.homeCustomExplorerPath; + return buildRoute((context) => ExplorerPage(path: path)); case HomeWidgetSettingsPage.routeName: return buildRoute((context) => HomeWidgetSettingsPage(widgetId: _widgetId!)); case ScreenSaverPage.routeName: diff --git a/lib/widgets/intent.dart b/lib/widgets/intent.dart index e0a97587e..da8f1dca5 100644 --- a/lib/widgets/intent.dart +++ b/lib/widgets/intent.dart @@ -15,6 +15,7 @@ class IntentDataKeys { static const action = 'action'; static const allowMultiple = 'allowMultiple'; static const brightness = 'brightness'; + static const explorerPath = 'explorerPath'; static const filters = 'filters'; static const mimeType = 'mimeType'; static const page = 'page'; diff --git a/lib/widgets/settings/navigation/navigation.dart b/lib/widgets/settings/navigation/navigation.dart index a23493848..c2b323957 100644 --- a/lib/widgets/settings/navigation/navigation.dart +++ b/lib/widgets/settings/navigation/navigation.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/services/common/services.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; +import 'package:aves/theme/text.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart'; @@ -43,24 +45,44 @@ class NavigationSection extends SettingsSection { class _HomeOption { final HomePageSetting page; final Set customCollection; + final String? customExplorerPath; const _HomeOption( this.page, { this.customCollection = const {}, + this.customExplorerPath, }); String getName(BuildContext context) { - if (page == HomePageSetting.collection && customCollection.isNotEmpty) { - return context.l10n.setHomeCustomCollection; + final pageName = page.getName(context); + switch (page) { + case HomePageSetting.collection: + return customCollection.isNotEmpty ? context.l10n.setHomeCustom : pageName; + case HomePageSetting.explorer: + return customExplorerPath != null ? context.l10n.setHomeCustom : pageName; + default: + return pageName; + } + } + + String? getDetails(BuildContext context) { + switch (page) { + case HomePageSetting.collection: + final filters = customCollection; + return filters.isNotEmpty ? [context.l10n.collectionPageTitle, filters.map((v) => v.getLabel(context)).join(', ')].join(AText.separator) : null; + case HomePageSetting.explorer: + final path = customExplorerPath; + return path != null ? [context.l10n.explorerPageTitle, pContext.basename(path)].join(AText.separator) : null; + default: + return null; } - return page.getName(context); } @override - bool operator ==(Object other) => identical(this, other) || other is _HomeOption && runtimeType == other.runtimeType && page == other.page && const DeepCollectionEquality().equals(customCollection, other.customCollection); + bool operator ==(Object other) => identical(this, other) || (other is _HomeOption && runtimeType == other.runtimeType && page == other.page && const DeepCollectionEquality().equals(customCollection, other.customCollection) && customExplorerPath == other.customExplorerPath); @override - int get hashCode => page.hashCode ^ customCollection.hashCode; + int get hashCode => page.hashCode ^ customCollection.hashCode ^ customExplorerPath.hashCode; } class SettingsTileNavigationHomePage extends SettingsTile { @@ -75,15 +97,18 @@ class SettingsTileNavigationHomePage extends SettingsTile { const _HomeOption(HomePageSetting.tags), const _HomeOption(HomePageSetting.explorer), if (settings.homeCustomCollection.isNotEmpty) _HomeOption(HomePageSetting.collection, customCollection: settings.homeCustomCollection), + if (settings.homeCustomExplorerPath != null) _HomeOption(HomePageSetting.explorer, customExplorerPath: settings.homeCustomExplorerPath), ], getName: (context, v) => v.getName(context), - selector: (context, s) => _HomeOption(s.homePage, customCollection: s.homeCustomCollection), - onSelection: (v) { - settings.homePage = v.page; - settings.homeCustomCollection = v.customCollection; - }, + selector: (context, s) => _HomeOption(s.homePage, customCollection: s.homeCustomCollection, customExplorerPath: s.homeCustomExplorerPath), + onSelection: (v) => settings.setHome( + v.page, + customCollection: v.customCollection, + customExplorerPath: v.customExplorerPath, + ), tileTitle: title(context), dialogTitle: context.l10n.settingsHomeDialogTitle, + optionSubtitleBuilder: (v) => v.getDetails(context), ); } diff --git a/plugins/aves_model/lib/aves_model.dart b/plugins/aves_model/lib/aves_model.dart index bef278c0c..e406bb5c4 100644 --- a/plugins/aves_model/lib/aves_model.dart +++ b/plugins/aves_model/lib/aves_model.dart @@ -1,6 +1,7 @@ library aves_model; export 'src/actions/chip.dart'; +export 'src/actions/explorer.dart'; export 'src/actions/chip_set.dart'; export 'src/actions/entry.dart'; export 'src/actions/entry_set.dart'; diff --git a/plugins/aves_model/lib/src/actions/explorer.dart b/plugins/aves_model/lib/src/actions/explorer.dart new file mode 100644 index 000000000..f71619c75 --- /dev/null +++ b/plugins/aves_model/lib/src/actions/explorer.dart @@ -0,0 +1,4 @@ +enum ExplorerAction { + addShortcut, + setHome, +} diff --git a/plugins/aves_model/lib/src/settings/keys.dart b/plugins/aves_model/lib/src/settings/keys.dart index 4517d18d9..a0ca10939 100644 --- a/plugins/aves_model/lib/src/settings/keys.dart +++ b/plugins/aves_model/lib/src/settings/keys.dart @@ -43,6 +43,7 @@ class SettingKeys { static const keepScreenOnKey = 'keep_screen_on'; static const homePageKey = 'home_page'; static const homeCustomCollectionKey = 'home_custom_collection'; + static const homeCustomExplorerPathKey = 'home_custom_explorer_path'; static const enableBottomNavigationBarKey = 'show_bottom_navigation_bar'; static const confirmCreateVaultKey = 'confirm_create_vault'; static const confirmDeleteForeverKey = 'confirm_delete_forever'; diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index 2013db5a3..48bdb8e91 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -30,8 +30,7 @@ Future configureAndLaunch() async { ..enableBlurEffect = true // navigation ..keepScreenOn = KeepScreenOn.always - ..homePage = HomePageSetting.collection - ..homeCustomCollection = {} + ..setHome(HomePageSetting.collection) ..enableBottomNavigationBar = true ..drawerTypeBookmarks = [null, FavouriteFilter.instance] // collection diff --git a/test_driver/driver_shaders.dart b/test_driver/driver_shaders.dart index 0d8382fc6..ac303883f 100644 --- a/test_driver/driver_shaders.dart +++ b/test_driver/driver_shaders.dart @@ -26,8 +26,7 @@ Future configureAndLaunch() async { ..enableBlurEffect = true // navigation ..keepScreenOn = KeepScreenOn.always - ..homePage = HomePageSetting.collection - ..homeCustomCollection = {} + ..setHome(HomePageSetting.collection) ..enableBottomNavigationBar = true // collection ..collectionSectionFactor = EntryGroupFactor.album