added search page as drawer option, tv: search in drawer

This commit is contained in:
Thibault Deckers 2022-12-16 00:43:50 +01:00
parent 386c1cf57d
commit 7acc72209b
15 changed files with 184 additions and 103 deletions

View file

@ -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;

View file

@ -123,7 +123,7 @@ class AvesApp extends StatefulWidget {
}
class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
final ValueNotifier<AppMode> appModeNotifier = ValueNotifier(AppMode.main);
final List<StreamSubscription> _subscriptions = [];
late final Future<void> _appSetup;
late final Future<bool> _shouldUseBoldFontLoader;
late final Future<CorePalette?> _dynamicColorPaletteLoader;
@ -138,6 +138,8 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
// - `OpenUpwardsPageTransitionsBuilder` on Pie / API 28
// - `ZoomPageTransitionsBuilder` on Android 10 / API 29 and above (default in Flutter v3.0.0)
final ValueNotifier<PageTransitionsBuilder> _pageTransitionsBuilderNotifier = ValueNotifier(const FadeUpwardsPageTransitionsBuilder());
final ValueNotifier<NavigationMode> _navigationModeNotifier = ValueNotifier(NavigationMode.traditional);
final ValueNotifier<AppMode> _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<AvesApp> 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<AvesApp> with WidgetsBindingObserver {
child: ChangeNotifierProvider<Settings>.value(
value: settings,
child: ListenableProvider<ValueNotifier<AppMode>>.value(
value: appModeNotifier,
value: _appModeNotifier,
child: Provider<CollectionSource>.value(
value: _mediaStoreSource,
child: Provider<TvRailController>.value(
@ -192,18 +206,16 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
: Scaffold(
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
);
return Selector<Settings, Tuple4<Locale?, bool, AvesThemeBrightness, bool>>(
selector: (context, s) => Tuple4(
return Selector<Settings, Tuple3<Locale?, AvesThemeBrightness, bool>>(
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<AvesApp> with WidgetsBindingObserver {
}
final lightTheme = Themes.lightTheme(lightAccent, initialized);
final darkTheme = themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent, initialized) : Themes.darkTheme(darkAccent, initialized);
return FutureBuilder<bool>(
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: <LogicalKeySet, Intent>{
// 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<PageTransitionsBuilder>(
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: <LogicalKeySet, Intent>{
// 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<AvesApp> with WidgetsBindingObserver {
);
}
Widget _decorateAppChild({
required BuildContext context,
required bool initialized,
required Widget? child,
}) {
if (initialized) {
WidgetsBinding.instance.addPostFrameCallback((_) => AvesApp.setSystemUIStyle(context));
}
return Selector<Settings, bool>(
selector: (context, s) => s.initialized ? s.accessibilityAnimations.animate : true,
builder: (context, areAnimationsEnabled, child) {
return FutureBuilder<bool>(
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<NavigationMode>(
valueListenable: _navigationModeNotifier,
builder: (context, navigationMode, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
boldText: shouldUseBoldFont,
navigationMode: navigationMode,
),
child: AvesColorsProvider(
child: ValueListenableBuilder<PageTransitionsBuilder>(
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<AvesApp> 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<AvesApp> 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<AvesApp> 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(

View file

@ -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;
}
}

View file

@ -36,6 +36,7 @@ class ColorListTile extends StatelessWidget {
onTap: () async {
final color = await showDialog<Color>(
context: context,
// TODO TLAD [tv] color pick
builder: (context) => ColorPickerDialog(
initialValue: value,
),

View file

@ -41,6 +41,7 @@ class _WheelSelectorState<T> extends State<WheelSelector<T>> {
const background = Colors.transparent;
final foreground = DefaultTextStyle.of(context).style.color!;
// TODO TLAD [tv] wheel traversal
return NotificationListener<ScrollNotification>(
// cancel notification bubbling so that the dialog scroll bar
// does not misinterpret wheel scrolling for dialog content scrolling

View file

@ -12,6 +12,8 @@ class SearchPage extends StatefulWidget {
final AvesSearchDelegate delegate;
final Animation<double> animation;
static const routeName = '/search';
const SearchPage({
super.key,
required this.delegate,

View file

@ -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<T extends CollectionFilter> 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<T extends CollectionFilter> 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<T extends CollectionFilter> 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<T extends CollectionFilter> with FeedbackMi
case ChipSetAction.rename:
return false;
case ChipSetAction.setCover:
return appMode == AppMode.main;
return isMain;
}
}

View file

@ -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<HomePage> {
static const allowedShortcutRoutes = [
CollectionPage.routeName,
AlbumListPage.routeName,
CollectionSearchDelegate.pageRouteName,
SearchPage.routeName,
];
@override
@ -168,7 +169,7 @@ class _HomePageState extends State<HomePage> {
_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<HomePage> {
widgetId: _widgetId!,
),
);
case CollectionSearchDelegate.pageRouteName:
case SearchPage.routeName:
return SearchPageRoute(
delegate: CollectionSearchDelegate(
searchFieldLabel: context.l10n.searchCollectionFieldHint,

View file

@ -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<CollectionSource>(),
),
);
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');
}
}
}

View file

@ -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:

View file

@ -200,12 +200,8 @@ class _TvRailState extends State<TvRail> {
);
Future<void> _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) {

View file

@ -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<String?> _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 ?? '';
}

View file

@ -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<NavigationDrawerEditorPage>
AlbumListPage.routeName,
CountryListPage.routeName,
TagListPage.routeName,
SearchPage.routeName,
};
@override

View file

@ -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:

View file

@ -30,6 +30,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
Stream<ActionEvent<EntryAction>> 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: