diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 123db2277..3efb2070f 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:aves/app_mode.dart'; import 'package:aves/model/entry/entry.dart'; @@ -171,7 +172,6 @@ class _CollectionAppBarState extends State with SingleTickerPr selector: (context, s) => s.collectionBrowsingQuickActions, builder: (context, _, child) { final useTvLayout = settings.useTvLayout; - final actions = _buildActions(context, selection); final onFilterTap = canRemoveFilters ? collection.removeFilter : null; return AvesAppBar( contentHeight: appBarContentHeight, @@ -181,7 +181,7 @@ class _CollectionAppBarState extends State with SingleTickerPr isSelecting: isSelecting, ), title: _buildAppBarTitle(isSelecting), - actions: useTvLayout ? [] : actions, + actions: (context, maxWidth) => useTvLayout ? [] : _buildActions(context, selection, maxWidth), bottom: Column( children: [ if (useTvLayout) @@ -190,7 +190,7 @@ class _CollectionAppBarState extends State with SingleTickerPr child: ListView( padding: const EdgeInsets.symmetric(horizontal: 8), scrollDirection: Axis.horizontal, - children: actions, + children: _buildActions(context, selection, double.infinity), ), ), if (showFilterBar) @@ -301,7 +301,7 @@ class _CollectionAppBarState extends State with SingleTickerPr } } - List _buildActions(BuildContext context, Selection selection) { + List _buildActions(BuildContext context, Selection selection, double maxWidth) { final appMode = context.watch>().value; final isSelecting = selection.isSelecting; final selectedItemCount = selection.selectedItems.length; @@ -333,6 +333,7 @@ class _CollectionAppBarState extends State with SingleTickerPr context: context, appMode: appMode, selection: selection, + maxWidth: maxWidth, isVisible: isVisible, canApply: canApply, ); @@ -366,20 +367,29 @@ class _CollectionAppBarState extends State with SingleTickerPr }).toList(); } + static double _iconButtonWidth(BuildContext context) { + const defaultPadding = EdgeInsets.all(8); + const defaultIconSize = 24.0; + return defaultPadding.horizontal + MediaQuery.textScalerOf(context).scale(defaultIconSize); + } + List _buildMobileActions({ required BuildContext context, required AppMode appMode, required Selection selection, + required double maxWidth, required bool Function(EntrySetAction action) isVisible, required bool Function(EntrySetAction action) canApply, }) { + final availableCount = (maxWidth / _iconButtonWidth(context)).floor(); + final isSelecting = selection.isSelecting; final selectedItemCount = selection.selectedItems.length; final hasSelection = selectedItemCount > 0; final browsingQuickActions = settings.collectionBrowsingQuickActions; final selectionQuickActions = isTrash ? [EntrySetAction.delete, EntrySetAction.restore] : settings.collectionSelectionQuickActions; - final quickActions = isSelecting ? selectionQuickActions : browsingQuickActions; + final quickActions = (isSelecting ? selectionQuickActions : browsingQuickActions).take(max(0, availableCount - 1)).toList(); final quickActionButtons = quickActions.where(isVisible).map( (action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection), ); @@ -396,7 +406,7 @@ class _CollectionAppBarState extends State with SingleTickerPr (action) => _toMenuItem(action, enabled: canApply(action), selection: selection), ); - final allContextualActions = isSelecting ? EntrySetActions.pageSelection: EntrySetActions.pageBrowsing; + final allContextualActions = isSelecting ? EntrySetActions.pageSelection : EntrySetActions.pageBrowsing; final contextualMenuActions = allContextualActions.where(_isValidForMenu).fold([], (prev, v) { if (v == null && (prev.isEmpty || prev.last == null)) return prev; return [...prev, v]; diff --git a/lib/widgets/common/identity/aves_app_bar.dart b/lib/widgets/common/identity/aves_app_bar.dart index 357c502d7..d0d7e6ba9 100644 --- a/lib/widgets/common/identity/aves_app_bar.dart +++ b/lib/widgets/common/identity/aves_app_bar.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/themes.dart'; @@ -13,12 +15,13 @@ class AvesAppBar extends StatelessWidget { final bool pinned; final Widget? leading; final Widget title; - final List actions; + final List Function(BuildContext context, double maxWidth) actions; final Widget? bottom; final Object? transitionKey; static const leadingHeroTag = 'appbar-leading'; static const titleHeroTag = 'appbar-title'; + static const double _titleMinWidth = 96; const AvesAppBar({ super.key, @@ -90,12 +93,16 @@ class AvesAppBar extends StatelessWidget { child: AnimatedSwitcher( duration: context.read().iconAnimation, child: FontSizeIconTheme( - child: Row( - key: ValueKey(transitionKey), - children: [ - Expanded(child: title), - ...actions, - ], + child: LayoutBuilder( + builder: (context, constraints) { + return Row( + key: ValueKey(transitionKey), + children: [ + Expanded(child: title), + ...(actions(context, max(0, constraints.maxWidth - _titleMinWidth))), + ], + ); + }, ), ), ), diff --git a/lib/widgets/explorer/app_bar.dart b/lib/widgets/explorer/app_bar.dart index 3b672c1c1..061b04435 100644 --- a/lib/widgets/explorer/app_bar.dart +++ b/lib/widgets/explorer/app_bar.dart @@ -53,44 +53,12 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse @override Widget build(BuildContext context) { - final animations = context.select((s) => s.accessibilityAnimations); return AvesAppBar( contentHeight: appBarContentHeight, pinned: true, leading: const DrawerButton(), title: _buildAppBarTitle(context), - actions: [ - IconButton( - icon: const Icon(AIcons.search), - 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, - ), - ), - ], + actions: _buildActions, bottom: LayoutBuilder( builder: (context, constraints) { return SizedBox( @@ -132,6 +100,42 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse ); } + List _buildActions(BuildContext context, double maxWidth) { + final animations = context.select((s) => s.accessibilityAnimations); + return [ + IconButton( + icon: const Icon(AIcons.search), + 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, + ), + ), + ]; + } + double get appBarContentHeight { final textScaler = MediaQuery.textScalerOf(context); return textScaler.scale(kToolbarHeight) + CrumbLine.getPreferredHeight(textScaler); diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index a9cf68144..9b3388588 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -141,9 +141,9 @@ class _FilterGridAppBarState( selector: (context, query) => query.enabled, builder: (context, queryEnabled, child) { - ActionsBuilder actionsBuilder = widget.actionsBuilder ?? _buildActions; + final actionDelegate = widget.actionDelegate; + final ActionsBuilder actionsBuilder = widget.actionsBuilder ?? _buildActions; final useTvLayout = settings.useTvLayout; - final actions = actionsBuilder(context, appMode, selection, widget.actionDelegate); return AvesAppBar( contentHeight: appBarContentHeight, pinned: context.select>, bool>((selection) => selection.isSelecting), @@ -152,7 +152,7 @@ class _FilterGridAppBarState useTvLayout ? [] : actionsBuilder(context, appMode, selection, actionDelegate), bottom: Column( children: [ if (useTvLayout) @@ -161,7 +161,7 @@ class _FilterGridAppBarState