fixed collection page app bar layout
This commit is contained in:
parent
27db528e67
commit
7777bf1550
4 changed files with 71 additions and 50 deletions
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
@ -171,7 +172,6 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
selector: (context, s) => s.collectionBrowsingQuickActions,
|
selector: (context, s) => s.collectionBrowsingQuickActions,
|
||||||
builder: (context, _, child) {
|
builder: (context, _, child) {
|
||||||
final useTvLayout = settings.useTvLayout;
|
final useTvLayout = settings.useTvLayout;
|
||||||
final actions = _buildActions(context, selection);
|
|
||||||
final onFilterTap = canRemoveFilters ? collection.removeFilter : null;
|
final onFilterTap = canRemoveFilters ? collection.removeFilter : null;
|
||||||
return AvesAppBar(
|
return AvesAppBar(
|
||||||
contentHeight: appBarContentHeight,
|
contentHeight: appBarContentHeight,
|
||||||
|
@ -181,7 +181,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
isSelecting: isSelecting,
|
isSelecting: isSelecting,
|
||||||
),
|
),
|
||||||
title: _buildAppBarTitle(isSelecting),
|
title: _buildAppBarTitle(isSelecting),
|
||||||
actions: useTvLayout ? [] : actions,
|
actions: (context, maxWidth) => useTvLayout ? [] : _buildActions(context, selection, maxWidth),
|
||||||
bottom: Column(
|
bottom: Column(
|
||||||
children: [
|
children: [
|
||||||
if (useTvLayout)
|
if (useTvLayout)
|
||||||
|
@ -190,7 +190,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: actions,
|
children: _buildActions(context, selection, double.infinity),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showFilterBar)
|
if (showFilterBar)
|
||||||
|
@ -301,7 +301,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildActions(BuildContext context, Selection<AvesEntry> selection) {
|
List<Widget> _buildActions(BuildContext context, Selection<AvesEntry> selection, double maxWidth) {
|
||||||
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||||
final isSelecting = selection.isSelecting;
|
final isSelecting = selection.isSelecting;
|
||||||
final selectedItemCount = selection.selectedItems.length;
|
final selectedItemCount = selection.selectedItems.length;
|
||||||
|
@ -333,6 +333,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
context: context,
|
context: context,
|
||||||
appMode: appMode,
|
appMode: appMode,
|
||||||
selection: selection,
|
selection: selection,
|
||||||
|
maxWidth: maxWidth,
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
canApply: canApply,
|
canApply: canApply,
|
||||||
);
|
);
|
||||||
|
@ -366,20 +367,29 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static double _iconButtonWidth(BuildContext context) {
|
||||||
|
const defaultPadding = EdgeInsets.all(8);
|
||||||
|
const defaultIconSize = 24.0;
|
||||||
|
return defaultPadding.horizontal + MediaQuery.textScalerOf(context).scale(defaultIconSize);
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> _buildMobileActions({
|
List<Widget> _buildMobileActions({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required AppMode appMode,
|
required AppMode appMode,
|
||||||
required Selection<AvesEntry> selection,
|
required Selection<AvesEntry> selection,
|
||||||
|
required double maxWidth,
|
||||||
required bool Function(EntrySetAction action) isVisible,
|
required bool Function(EntrySetAction action) isVisible,
|
||||||
required bool Function(EntrySetAction action) canApply,
|
required bool Function(EntrySetAction action) canApply,
|
||||||
}) {
|
}) {
|
||||||
|
final availableCount = (maxWidth / _iconButtonWidth(context)).floor();
|
||||||
|
|
||||||
final isSelecting = selection.isSelecting;
|
final isSelecting = selection.isSelecting;
|
||||||
final selectedItemCount = selection.selectedItems.length;
|
final selectedItemCount = selection.selectedItems.length;
|
||||||
final hasSelection = selectedItemCount > 0;
|
final hasSelection = selectedItemCount > 0;
|
||||||
|
|
||||||
final browsingQuickActions = settings.collectionBrowsingQuickActions;
|
final browsingQuickActions = settings.collectionBrowsingQuickActions;
|
||||||
final selectionQuickActions = isTrash ? [EntrySetAction.delete, EntrySetAction.restore] : settings.collectionSelectionQuickActions;
|
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(
|
final quickActionButtons = quickActions.where(isVisible).map(
|
||||||
(action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection),
|
(action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection),
|
||||||
);
|
);
|
||||||
|
@ -396,7 +406,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
(action) => _toMenuItem(action, enabled: canApply(action), selection: selection),
|
(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(<EntrySetAction?>[], (prev, v) {
|
final contextualMenuActions = allContextualActions.where(_isValidForMenu).fold(<EntrySetAction?>[], (prev, v) {
|
||||||
if (v == null && (prev.isEmpty || prev.last == null)) return prev;
|
if (v == null && (prev.isEmpty || prev.last == null)) return prev;
|
||||||
return [...prev, v];
|
return [...prev, v];
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
|
@ -13,12 +15,13 @@ class AvesAppBar extends StatelessWidget {
|
||||||
final bool pinned;
|
final bool pinned;
|
||||||
final Widget? leading;
|
final Widget? leading;
|
||||||
final Widget title;
|
final Widget title;
|
||||||
final List<Widget> actions;
|
final List<Widget> Function(BuildContext context, double maxWidth) actions;
|
||||||
final Widget? bottom;
|
final Widget? bottom;
|
||||||
final Object? transitionKey;
|
final Object? transitionKey;
|
||||||
|
|
||||||
static const leadingHeroTag = 'appbar-leading';
|
static const leadingHeroTag = 'appbar-leading';
|
||||||
static const titleHeroTag = 'appbar-title';
|
static const titleHeroTag = 'appbar-title';
|
||||||
|
static const double _titleMinWidth = 96;
|
||||||
|
|
||||||
const AvesAppBar({
|
const AvesAppBar({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -90,12 +93,16 @@ class AvesAppBar extends StatelessWidget {
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: context.read<DurationsData>().iconAnimation,
|
duration: context.read<DurationsData>().iconAnimation,
|
||||||
child: FontSizeIconTheme(
|
child: FontSizeIconTheme(
|
||||||
child: Row(
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return Row(
|
||||||
key: ValueKey(transitionKey),
|
key: ValueKey(transitionKey),
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: title),
|
Expanded(child: title),
|
||||||
...actions,
|
...(actions(context, max(0, constraints.maxWidth - _titleMinWidth))),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -53,44 +53,12 @@ class _ExplorerAppBarState extends State<ExplorerAppBar> with WidgetsBindingObse
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
|
|
||||||
return AvesAppBar(
|
return AvesAppBar(
|
||||||
contentHeight: appBarContentHeight,
|
contentHeight: appBarContentHeight,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
leading: const DrawerButton(),
|
leading: const DrawerButton(),
|
||||||
title: _buildAppBarTitle(context),
|
title: _buildAppBarTitle(context),
|
||||||
actions: [
|
actions: _buildActions,
|
||||||
IconButton(
|
|
||||||
icon: const Icon(AIcons.search),
|
|
||||||
onPressed: () => _goToSearch(context),
|
|
||||||
tooltip: MaterialLocalizations.of(context).searchFieldLabel,
|
|
||||||
),
|
|
||||||
if (_volumes.length > 1)
|
|
||||||
FontSizeIconTheme(
|
|
||||||
child: PopupMenuButton<StorageVolume>(
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
bottom: LayoutBuilder(
|
bottom: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
@ -132,6 +100,42 @@ class _ExplorerAppBarState extends State<ExplorerAppBar> with WidgetsBindingObse
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildActions(BuildContext context, double maxWidth) {
|
||||||
|
final animations = context.select<Settings, AccessibilityAnimations>((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<StorageVolume>(
|
||||||
|
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 {
|
double get appBarContentHeight {
|
||||||
final textScaler = MediaQuery.textScalerOf(context);
|
final textScaler = MediaQuery.textScalerOf(context);
|
||||||
return textScaler.scale(kToolbarHeight) + CrumbLine.getPreferredHeight(textScaler);
|
return textScaler.scale(kToolbarHeight) + CrumbLine.getPreferredHeight(textScaler);
|
||||||
|
|
|
@ -141,9 +141,9 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
|
||||||
child: Selector<Query, bool>(
|
child: Selector<Query, bool>(
|
||||||
selector: (context, query) => query.enabled,
|
selector: (context, query) => query.enabled,
|
||||||
builder: (context, queryEnabled, child) {
|
builder: (context, queryEnabled, child) {
|
||||||
ActionsBuilder<T, CSAD> actionsBuilder = widget.actionsBuilder ?? _buildActions;
|
final actionDelegate = widget.actionDelegate;
|
||||||
|
final ActionsBuilder<T, CSAD> actionsBuilder = widget.actionsBuilder ?? _buildActions;
|
||||||
final useTvLayout = settings.useTvLayout;
|
final useTvLayout = settings.useTvLayout;
|
||||||
final actions = actionsBuilder(context, appMode, selection, widget.actionDelegate);
|
|
||||||
return AvesAppBar(
|
return AvesAppBar(
|
||||||
contentHeight: appBarContentHeight,
|
contentHeight: appBarContentHeight,
|
||||||
pinned: context.select<Selection<FilterGridItem<T>>, bool>((selection) => selection.isSelecting),
|
pinned: context.select<Selection<FilterGridItem<T>>, bool>((selection) => selection.isSelecting),
|
||||||
|
@ -152,7 +152,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
|
||||||
isSelecting: isSelecting,
|
isSelecting: isSelecting,
|
||||||
),
|
),
|
||||||
title: _buildAppBarTitle(isSelecting),
|
title: _buildAppBarTitle(isSelecting),
|
||||||
actions: useTvLayout ? [] : actions,
|
actions: (context, maxWidth) => useTvLayout ? [] : actionsBuilder(context, appMode, selection, actionDelegate),
|
||||||
bottom: Column(
|
bottom: Column(
|
||||||
children: [
|
children: [
|
||||||
if (useTvLayout)
|
if (useTvLayout)
|
||||||
|
@ -161,7 +161,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: actions,
|
children: actionsBuilder(context, appMode, selection, actionDelegate),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (queryEnabled)
|
if (queryEnabled)
|
||||||
|
|
Loading…
Reference in a new issue