diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index f07066f25..5c5a11ca5 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -17,8 +17,13 @@ import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/services/common/optional_event_channel.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/aves_app.dart'; +import 'package:aves/widgets/common/search/page.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:aves_map/aves_map.dart'; import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:latlong2/latlong.dart'; @@ -246,6 +251,12 @@ class Settings extends ChangeNotifier { FavouriteFilter.instance, RecentlyAddedFilter.instance, ]; + drawerPageBookmarks = [ + AlbumListPage.routeName, + CountryListPage.routeName, + TagListPage.routeName, + SearchPage.routeName, + ]; showOverlayOnOpening = false; showOverlayMinimap = false; showOverlayThumbnailPreview = false; diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 53eb06012..f4f672758 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -123,7 +123,7 @@ class AvesApp extends StatefulWidget { } class _AvesAppState extends State with WidgetsBindingObserver { - final ValueNotifier appModeNotifier = ValueNotifier(AppMode.main); + final List _subscriptions = []; late final Future _appSetup; late final Future _shouldUseBoldFontLoader; late final Future _dynamicColorPaletteLoader; @@ -138,6 +138,8 @@ class _AvesAppState extends State with WidgetsBindingObserver { // - `OpenUpwardsPageTransitionsBuilder` on Pie / API 28 // - `ZoomPageTransitionsBuilder` on Android 10 / API 29 and above (default in Flutter v3.0.0) final ValueNotifier _pageTransitionsBuilderNotifier = ValueNotifier(const FadeUpwardsPageTransitionsBuilder()); + final ValueNotifier _navigationModeNotifier = ValueNotifier(NavigationMode.traditional); + final ValueNotifier _appModeNotifier = ValueNotifier(AppMode.main); // observers are not registered when using the same list object with different items // the list itself needs to be reassigned @@ -156,13 +158,25 @@ class _AvesAppState extends State with WidgetsBindingObserver { _screenSize = _getScreenSize(); _shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont(); _dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette(); - _mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChanged(event as String?)); - _newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?)); - _analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()); - _errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?)); + _subscriptions.add(_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChanged(event as String?))); + _subscriptions.add(_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?))); + _subscriptions.add(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion())); + _subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?))); WidgetsBinding.instance.addObserver(this); } + @override + void dispose() { + _pageTransitionsBuilderNotifier.dispose(); + _navigationModeNotifier.dispose(); + _appModeNotifier.dispose(); + _subscriptions + ..forEach((sub) => sub.cancel()) + ..clear(); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + @override Widget build(BuildContext context) { // place the settings provider above `MaterialApp` @@ -172,7 +186,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { child: ChangeNotifierProvider.value( value: settings, child: ListenableProvider>.value( - value: appModeNotifier, + value: _appModeNotifier, child: Provider.value( value: _mediaStoreSource, child: Provider.value( @@ -192,18 +206,16 @@ class _AvesAppState extends State with WidgetsBindingObserver { : Scaffold( body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(), ); - return Selector>( - selector: (context, s) => Tuple4( + return Selector>( + selector: (context, s) => Tuple3( s.locale, - s.initialized ? s.accessibilityAnimations.animate : true, s.initialized ? s.themeBrightness : SettingsDefaults.themeBrightness, s.initialized ? s.enableDynamicColor : SettingsDefaults.enableDynamicColor, ), builder: (context, s, child) { final settingsLocale = s.item1; - final areAnimationsEnabled = s.item2; - final themeBrightness = s.item3; - final enableDynamicColor = s.item4; + final themeBrightness = s.item2; + final enableDynamicColor = s.item3; Constants.updateStylesForLocale(settings.appliedLocale); @@ -222,58 +234,30 @@ class _AvesAppState extends State with WidgetsBindingObserver { } final lightTheme = Themes.lightTheme(lightAccent, initialized); final darkTheme = themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent, initialized) : Themes.darkTheme(darkAccent, initialized); - return FutureBuilder( - future: _shouldUseBoldFontLoader, - builder: (context, snapshot) { - // Flutter v3.4 already checks the system `Configuration.fontWeightAdjustment` to update `MediaQuery` - // but we need to also check the non-standard Samsung field `bf` representing the bold font toggle - final shouldUseBoldFont = snapshot.data ?? false; - return Shortcuts( - shortcuts: { - // handle Android TV remote `select` button - LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), - }, - child: MaterialApp( - navigatorKey: AvesApp.navigatorKey, - home: home, - navigatorObservers: _navigatorObservers, - builder: (context, child) { - if (initialized) { - WidgetsBinding.instance.addPostFrameCallback((_) => AvesApp.setSystemUIStyle(context)); - } - return MediaQuery( - data: MediaQuery.of(context).copyWith(boldText: shouldUseBoldFont), - child: AvesColorsProvider( - child: ValueListenableBuilder( - valueListenable: _pageTransitionsBuilderNotifier, - builder: (context, pageTransitionsBuilder, child) { - return Theme( - data: Theme.of(context).copyWith( - pageTransitionsTheme: areAnimationsEnabled - ? PageTransitionsTheme(builders: {TargetPlatform.android: pageTransitionsBuilder}) - // strip page transitions used by `MaterialPageRoute` - : const DirectPageTransitionsTheme(), - ), - child: MediaQueryDataProvider(child: child!), - ); - }, - child: child, - ), - ), - ); - }, - onGenerateTitle: (context) => context.l10n.appName, - theme: lightTheme, - darkTheme: darkTheme, - themeMode: themeBrightness.appThemeMode, - locale: settingsLocale, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AvesApp.supportedLocales, - // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906 - scrollBehavior: StretchMaterialScrollBehavior(), - ), - ); + return Shortcuts( + shortcuts: { + // handle Android TV remote `select` button + LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), }, + child: MaterialApp( + navigatorKey: AvesApp.navigatorKey, + home: home, + navigatorObservers: _navigatorObservers, + builder: (context, child) => _decorateAppChild( + context: context, + initialized: initialized, + child: child, + ), + onGenerateTitle: (context) => context.l10n.appName, + theme: lightTheme, + darkTheme: darkTheme, + themeMode: themeBrightness.appThemeMode, + locale: settingsLocale, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AvesApp.supportedLocales, + // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906 + scrollBehavior: StretchMaterialScrollBehavior(), + ), ); }, ); @@ -291,6 +275,59 @@ class _AvesAppState extends State with WidgetsBindingObserver { ); } + Widget _decorateAppChild({ + required BuildContext context, + required bool initialized, + required Widget? child, + }) { + if (initialized) { + WidgetsBinding.instance.addPostFrameCallback((_) => AvesApp.setSystemUIStyle(context)); + } + return Selector( + selector: (context, s) => s.initialized ? s.accessibilityAnimations.animate : true, + builder: (context, areAnimationsEnabled, child) { + return FutureBuilder( + future: _shouldUseBoldFontLoader, + builder: (context, snapshot) { + // Flutter v3.4 already checks the system `Configuration.fontWeightAdjustment` to update `MediaQuery` + // but we need to also check the non-standard Samsung field `bf` representing the bold font toggle + final shouldUseBoldFont = snapshot.data ?? false; + return ValueListenableBuilder( + valueListenable: _navigationModeNotifier, + builder: (context, navigationMode, child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + boldText: shouldUseBoldFont, + navigationMode: navigationMode, + ), + child: AvesColorsProvider( + child: ValueListenableBuilder( + valueListenable: _pageTransitionsBuilderNotifier, + builder: (context, pageTransitionsBuilder, child) { + return Theme( + data: Theme.of(context).copyWith( + pageTransitionsTheme: areAnimationsEnabled + ? PageTransitionsTheme(builders: {TargetPlatform.android: pageTransitionsBuilder}) + // strip page transitions used by `MaterialPageRoute` + : const DirectPageTransitionsTheme(), + ), + child: MediaQueryDataProvider(child: child!), + ); + }, + child: child, + ), + ), + ); + }, + child: child, + ); + }, + ); + }, + child: child, + ); + } + Widget _buildError(Object error) { return Container( alignment: Alignment.center, @@ -311,7 +348,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { debugPrint('$runtimeType lifecycle ${state.name}'); switch (state) { case AppLifecycleState.inactive: - switch (appModeNotifier.value) { + switch (_appModeNotifier.value) { case AppMode.main: case AppMode.pickSingleMediaExternal: case AppMode.pickMultipleMediaExternal: @@ -370,6 +407,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { await device.init(); if (device.isTelevision) { _pageTransitionsBuilderNotifier.value = const TvPageTransitionsBuilder(); + _navigationModeNotifier.value = NavigationMode.directional; } await mobileServices.init(); await settings.init(monitorPlatformSettings: true); @@ -440,7 +478,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { debugPrint('$runtimeType onNewIntent with intentData=$intentData'); // do not reset when relaunching the app - if (appModeNotifier.value == AppMode.main && (intentData == null || intentData.isEmpty == true)) return; + if (_appModeNotifier.value == AppMode.main && (intentData == null || intentData.isEmpty == true)) return; reportService.log('New intent'); AvesApp.navigatorKey.currentState!.pushReplacement(DirectMaterialPageRoute( diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index c853a2988..93b35158f 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -55,6 +55,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware required int selectedItemCount, required bool isTrash, }) { + final canWrite = !device.isReadOnly; + final isMain = appMode == AppMode.main; switch (action) { // general case EntrySetAction.configureView: @@ -67,26 +69,26 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware return isSelecting && selectedItemCount == itemCount; // browsing case EntrySetAction.searchCollection: - return appMode.canNavigate && !isSelecting; + return !device.isTelevision && appMode.canNavigate && !isSelecting; case EntrySetAction.toggleTitleSearch: return !isSelecting; case EntrySetAction.addShortcut: - return appMode == AppMode.main && !isSelecting && device.canPinShortcut && !isTrash; + return isMain && !isSelecting && device.canPinShortcut && !isTrash; case EntrySetAction.emptyBin: - return !device.isReadOnly && appMode == AppMode.main && isTrash; + return canWrite && isMain && isTrash; // browsing or selecting case EntrySetAction.map: case EntrySetAction.slideshow: case EntrySetAction.stats: - return appMode == AppMode.main; + return isMain; case EntrySetAction.rescan: - return appMode == AppMode.main && !isTrash; + return !device.isTelevision && isMain && !isTrash; // selecting case EntrySetAction.share: case EntrySetAction.toggleFavourite: - return appMode == AppMode.main && isSelecting && !isTrash; + return isMain && isSelecting && !isTrash; case EntrySetAction.delete: - return !device.isReadOnly && appMode == AppMode.main && isSelecting; + return canWrite && isMain && isSelecting; case EntrySetAction.copy: case EntrySetAction.move: case EntrySetAction.rename: @@ -99,9 +101,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware case EntrySetAction.editRating: case EntrySetAction.editTags: case EntrySetAction.removeMetadata: - return !device.isReadOnly && appMode == AppMode.main && isSelecting && !isTrash; + return canWrite && isMain && isSelecting && !isTrash; case EntrySetAction.restore: - return !device.isReadOnly && appMode == AppMode.main && isSelecting && isTrash; + return canWrite && isMain && isSelecting && isTrash; } } diff --git a/lib/widgets/common/basic/color_list_tile.dart b/lib/widgets/common/basic/color_list_tile.dart index cbaec158c..076957718 100644 --- a/lib/widgets/common/basic/color_list_tile.dart +++ b/lib/widgets/common/basic/color_list_tile.dart @@ -36,6 +36,7 @@ class ColorListTile extends StatelessWidget { onTap: () async { final color = await showDialog( context: context, + // TODO TLAD [tv] color pick builder: (context) => ColorPickerDialog( initialValue: value, ), diff --git a/lib/widgets/common/basic/wheel.dart b/lib/widgets/common/basic/wheel.dart index 1f47a87af..ace7c394d 100644 --- a/lib/widgets/common/basic/wheel.dart +++ b/lib/widgets/common/basic/wheel.dart @@ -41,6 +41,7 @@ class _WheelSelectorState extends State> { const background = Colors.transparent; final foreground = DefaultTextStyle.of(context).style.color!; + // TODO TLAD [tv] wheel traversal return NotificationListener( // cancel notification bubbling so that the dialog scroll bar // does not misinterpret wheel scrolling for dialog content scrolling diff --git a/lib/widgets/common/search/page.dart b/lib/widgets/common/search/page.dart index 6a3db64c7..edb1d304f 100644 --- a/lib/widgets/common/search/page.dart +++ b/lib/widgets/common/search/page.dart @@ -12,6 +12,8 @@ class SearchPage extends StatefulWidget { final AvesSearchDelegate delegate; final Animation animation; + static const routeName = '/search'; + const SearchPage({ super.key, required this.delegate, diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index c9795de39..d6341bac1 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -1,6 +1,7 @@ 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/device.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; @@ -68,6 +69,7 @@ abstract class ChipSetActionDelegate with FeedbackMi }) { final selectedItemCount = selectedFilters.length; final hasSelection = selectedFilters.isNotEmpty; + final isMain = appMode == AppMode.main; switch (action) { // general case ChipSetAction.configureView: @@ -80,7 +82,7 @@ abstract class ChipSetActionDelegate with FeedbackMi return isSelecting && selectedItemCount == itemCount; // browsing case ChipSetAction.search: - return appMode.canNavigate && !isSelecting; + return !device.isTelevision && appMode.canNavigate && !isSelecting; case ChipSetAction.toggleTitleSearch: return !isSelecting; case ChipSetAction.createAlbum: @@ -89,12 +91,12 @@ abstract class ChipSetActionDelegate with FeedbackMi case ChipSetAction.map: case ChipSetAction.slideshow: case ChipSetAction.stats: - return appMode == AppMode.main; + return isMain; // selecting (single/multiple filters) case ChipSetAction.delete: return false; case ChipSetAction.hide: - return appMode == AppMode.main; + return isMain; case ChipSetAction.pin: return !hasSelection || !settings.pinnedFilters.containsAll(selectedFilters); case ChipSetAction.unpin: @@ -103,7 +105,7 @@ abstract class ChipSetActionDelegate with FeedbackMi case ChipSetAction.rename: return false; case ChipSetAction.setCover: - return appMode == AppMode.main; + return isMain; } } diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 3f84fb4ca..91b391e53 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -19,6 +19,7 @@ import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/search/search_delegate.dart'; @@ -75,7 +76,7 @@ class _HomePageState extends State { static const allowedShortcutRoutes = [ CollectionPage.routeName, AlbumListPage.routeName, - CollectionSearchDelegate.pageRouteName, + SearchPage.routeName, ]; @override @@ -168,7 +169,7 @@ class _HomePageState extends State { _initialRouteName = ScreenSaverSettingsPage.routeName; break; case actionSearch: - _initialRouteName = CollectionSearchDelegate.pageRouteName; + _initialRouteName = SearchPage.routeName; _initialSearchQuery = intentData[intentDataKeyQuery]; break; case actionSetWallpaper: @@ -363,7 +364,7 @@ class _HomePageState extends State { widgetId: _widgetId!, ), ); - case CollectionSearchDelegate.pageRouteName: + case SearchPage.routeName: return SearchPageRoute( delegate: CollectionSearchDelegate( searchFieldLabel: context.l10n.searchCollectionFieldHint, diff --git a/lib/widgets/navigation/drawer/page_nav_tile.dart b/lib/widgets/navigation/drawer/page_nav_tile.dart index 92fe5b76f..bb69ee34a 100644 --- a/lib/widgets/navigation/drawer/page_nav_tile.dart +++ b/lib/widgets/navigation/drawer/page_nav_tile.dart @@ -1,12 +1,17 @@ +import 'package:aves/model/source/collection_source.dart'; import 'package:aves/widgets/about/about_page.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/search/page.dart'; +import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/debug/app_debug_page.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:aves/widgets/navigation/drawer/tile.dart'; +import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/settings/settings_page.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class PageNavTile extends StatelessWidget { final Widget? trailing; @@ -42,10 +47,7 @@ class PageNavTile extends StatelessWidget { : null, onTap: () { Navigator.pop(context); - final route = MaterialPageRoute( - settings: RouteSettings(name: routeName), - builder: pageBuilder(routeName), - ); + final route = routeBuilder(context, routeName); if (topLevel) { Navigator.pushAndRemoveUntil( context, @@ -61,8 +63,25 @@ class PageNavTile extends StatelessWidget { ); } - static WidgetBuilder pageBuilder(String route) { - switch (route) { + static Route routeBuilder(BuildContext context, String routeName) { + switch (routeName) { + case SearchPage.routeName: + return SearchPageRoute( + delegate: CollectionSearchDelegate( + searchFieldLabel: context.l10n.searchCollectionFieldHint, + source: context.read(), + ), + ); + default: + return MaterialPageRoute( + settings: RouteSettings(name: routeName), + builder: _materialPageBuilder(routeName), + ); + } + } + + static WidgetBuilder _materialPageBuilder(String routeName) { + switch (routeName) { case AlbumListPage.routeName: return (_) => const AlbumListPage(); case CountryListPage.routeName: @@ -76,7 +95,7 @@ class PageNavTile extends StatelessWidget { case AppDebugPage.routeName: return (_) => const AppDebugPage(); default: - throw Exception('unknown route=$route'); + throw Exception('unknown route=$routeName'); } } } diff --git a/lib/widgets/navigation/nav_display.dart b/lib/widgets/navigation/nav_display.dart index bc4ed12d8..b21f7114f 100644 --- a/lib/widgets/navigation/nav_display.dart +++ b/lib/widgets/navigation/nav_display.dart @@ -5,13 +5,13 @@ import 'package:aves/model/filters/type.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/about/about_page.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/debug/app_debug_page.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:aves/widgets/settings/settings_page.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; class NavigationDisplay { static String getFilterTitle(BuildContext context, CollectionFilter? filter) { @@ -41,6 +41,8 @@ class NavigationDisplay { return l10n.settingsPageTitle; case AboutPage.routeName: return l10n.aboutPageTitle; + case SearchPage.routeName: + return MaterialLocalizations.of(context).searchFieldLabel; case AppDebugPage.routeName: return 'Debug'; default: @@ -60,6 +62,8 @@ class NavigationDisplay { return AIcons.settings; case AboutPage.routeName: return AIcons.info; + case SearchPage.routeName: + return AIcons.search; case AppDebugPage.routeName: return AIcons.debug; default: diff --git a/lib/widgets/navigation/tv_rail.dart b/lib/widgets/navigation/tv_rail.dart index 560ee9db6..eeecd3840 100644 --- a/lib/widgets/navigation/tv_rail.dart +++ b/lib/widgets/navigation/tv_rail.dart @@ -200,12 +200,8 @@ class _TvRailState extends State { ); Future _goTo(String routeName) async { - await Navigator.push( - context, - MaterialPageRoute( - settings: RouteSettings(name: routeName), - builder: PageNavTile.pageBuilder(routeName), - )); + // TODO TLAD [tv] check `topLevel` / `Navigator.pushAndRemoveUntil` + await Navigator.push(context, PageNavTile.routeBuilder(context, routeName)); } void _goToCollection(BuildContext context, CollectionFilter? filter) { diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index e6259006a..8cb400725 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -23,6 +23,7 @@ import 'package:aves/widgets/common/expandable_filter_row.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/search/delegate.dart'; +import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -33,7 +34,6 @@ class CollectionSearchDelegate extends AvesSearchDelegate { final CollectionLens? parentCollection; final ValueNotifier _expandedSectionNotifier = ValueNotifier(null); - static const pageRouteName = '/search'; static const int searchHistoryCount = 10; static final typeFilters = [ FavouriteFilter.instance, @@ -59,7 +59,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate { super.canPop, String? initialQuery, }) : super( - routeName: pageRouteName, + routeName: SearchPage.routeName, ) { query = initialQuery ?? ''; } diff --git a/lib/widgets/settings/navigation/drawer.dart b/lib/widgets/settings/navigation/drawer.dart index bb17b8fbf..e0a65ef2d 100644 --- a/lib/widgets/settings/navigation/drawer.dart +++ b/lib/widgets/settings/navigation/drawer.dart @@ -2,6 +2,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/recent.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/search/page.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'; @@ -39,6 +40,7 @@ class _NavigationDrawerEditorPageState extends State AlbumListPage.routeName, CountryListPage.routeName, TagListPage.routeName, + SearchPage.routeName, }; @override diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index f8e926cbb..9f05f2790 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -67,6 +67,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } } else { final targetEntry = EntryActions.pageActions.contains(action) ? pageEntry : mainEntry; + final canWrite = !device.isReadOnly; switch (action) { case EntryAction.toggleFavourite: return collection != null; @@ -75,14 +76,14 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.move: return targetEntry.canEdit; case EntryAction.copy: - return !device.isReadOnly; + return canWrite; case EntryAction.rotateCCW: case EntryAction.rotateCW: return targetEntry.canRotate; case EntryAction.flip: return targetEntry.canFlip; case EntryAction.convert: - return !device.isReadOnly && !targetEntry.isVideo; + return canWrite && !targetEntry.isVideo; case EntryAction.print: return device.canPrint && !targetEntry.isVideo; case EntryAction.openMap: @@ -90,7 +91,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.viewSource: return targetEntry.isSvg; case EntryAction.videoCaptureFrame: - return !device.isReadOnly && targetEntry.isVideo; + return canWrite && targetEntry.isVideo; case EntryAction.videoToggleMute: return !device.isTelevision && targetEntry.isVideo; case EntryAction.videoSelectStreams: @@ -106,7 +107,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.addShortcut: return device.canPinShortcut; case EntryAction.edit: - return !device.isReadOnly; + return canWrite; case EntryAction.copyToClipboard: return !device.isTelevision; case EntryAction.info: diff --git a/lib/widgets/viewer/action/entry_info_action_delegate.dart b/lib/widgets/viewer/action/entry_info_action_delegate.dart index 162cb0db0..7abc986a9 100644 --- a/lib/widgets/viewer/action/entry_info_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_info_action_delegate.dart @@ -30,6 +30,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi Stream> get eventStream => _eventStreamController.stream; bool isVisible(AvesEntry targetEntry, EntryAction action) { + final canWrite = !device.isReadOnly; switch (action) { // general case EntryAction.editDate: @@ -39,13 +40,13 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi case EntryAction.editTags: case EntryAction.removeMetadata: case EntryAction.exportMetadata: - return !device.isReadOnly; + return canWrite; // GeoTIFF case EntryAction.showGeoTiffOnMap: return targetEntry.isGeotiff; // motion photo case EntryAction.convertMotionPhotoToStillImage: - return !device.isReadOnly && targetEntry.isMotionPhoto; + return canWrite && targetEntry.isMotionPhoto; case EntryAction.viewMotionPhotoVideo: return targetEntry.isMotionPhoto; default: