#437 tv: collection app bar
This commit is contained in:
parent
3ca33d0608
commit
6c71752f18
4 changed files with 111 additions and 27 deletions
|
@ -32,6 +32,7 @@ import 'package:aves/widgets/common/search/route.dart';
|
|||
import 'package:aves/widgets/dialogs/tile_view_dialog.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
|
||||
import 'package:aves/widgets/search/search_delegate.dart';
|
||||
import 'package:aves/widgets/settings/common/quick_actions/action_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -159,6 +160,8 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
return Selector<Settings, List<EntrySetAction>>(
|
||||
selector: (context, s) => s.collectionBrowsingQuickActions,
|
||||
builder: (context, _, child) {
|
||||
final isTelevision = device.isTelevision;
|
||||
final actions = _buildActions(context, selection);
|
||||
return AvesAppBar(
|
||||
contentHeight: appBarContentHeight,
|
||||
leading: _buildAppBarLeading(
|
||||
|
@ -166,9 +169,18 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
isSelecting: isSelecting,
|
||||
),
|
||||
title: _buildAppBarTitle(isSelecting),
|
||||
actions: _buildActions(context, selection),
|
||||
actions: isTelevision ? [] : actions,
|
||||
bottom: Column(
|
||||
children: [
|
||||
if (isTelevision)
|
||||
SizedBox(
|
||||
height: tvActionButtonHeight,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: actions,
|
||||
),
|
||||
),
|
||||
if (showFilterBar)
|
||||
NotificationListener<ReverseFilterNotification>(
|
||||
onNotification: (notification) {
|
||||
|
@ -198,12 +210,32 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
);
|
||||
}
|
||||
|
||||
double get appBarContentHeight {
|
||||
final hasQuery = context.read<Query>().enabled;
|
||||
return kToolbarHeight + (showFilterBar ? FilterBar.preferredHeight : .0) + (hasQuery ? EntryQueryBar.preferredHeight : .0);
|
||||
double get tvActionButtonHeight {
|
||||
final text = [
|
||||
...EntrySetActions.general,
|
||||
...EntrySetActions.pageBrowsing,
|
||||
...EntrySetActions.pageSelection,
|
||||
].map((action) => action.getText(context)).fold('', (prev, v) => v.length > prev.length ? v : prev);
|
||||
return ActionButton.getSize(context, text, showCaption: true).height;
|
||||
}
|
||||
|
||||
Widget _buildAppBarLeading({required bool hasDrawer, required bool isSelecting}) {
|
||||
double get appBarContentHeight {
|
||||
double height = kToolbarHeight;
|
||||
if (device.isTelevision) {
|
||||
height += tvActionButtonHeight;
|
||||
}
|
||||
if (showFilterBar) {
|
||||
height += FilterBar.preferredHeight;
|
||||
}
|
||||
if (context.read<Query>().enabled) {
|
||||
height += EntryQueryBar.preferredHeight;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
Widget? _buildAppBarLeading({required bool hasDrawer, required bool isSelecting}) {
|
||||
if (device.isTelevision) return null;
|
||||
|
||||
if (!hasDrawer) {
|
||||
return const CloseButton();
|
||||
}
|
||||
|
@ -265,10 +297,10 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
}
|
||||
|
||||
List<Widget> _buildActions(BuildContext context, Selection<AvesEntry> selection) {
|
||||
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||
final isSelecting = selection.isSelecting;
|
||||
final selectedItemCount = selection.selectedItems.length;
|
||||
|
||||
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||
bool isVisible(EntrySetAction action) => _actionDelegate.isVisible(
|
||||
action,
|
||||
appMode: appMode,
|
||||
|
@ -283,12 +315,58 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
itemCount: collection.entryCount,
|
||||
selectedItemCount: selectedItemCount,
|
||||
);
|
||||
final canApplyEditActions = selectedItemCount > 0;
|
||||
|
||||
return device.isTelevision
|
||||
? _buildTelevisionActions(
|
||||
appMode: appMode,
|
||||
selection: selection,
|
||||
isVisible: isVisible,
|
||||
canApply: canApply,
|
||||
)
|
||||
: _buildMobileActions(
|
||||
appMode: appMode,
|
||||
selection: selection,
|
||||
isVisible: isVisible,
|
||||
canApply: canApply,
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildTelevisionActions({
|
||||
required AppMode appMode,
|
||||
required Selection<AvesEntry> selection,
|
||||
required bool Function(EntrySetAction action) isVisible,
|
||||
required bool Function(EntrySetAction action) canApply,
|
||||
}) {
|
||||
final isSelecting = selection.isSelecting;
|
||||
|
||||
return [
|
||||
...EntrySetActions.general,
|
||||
...isSelecting ? EntrySetActions.pageSelection : EntrySetActions.pageBrowsing,
|
||||
].where(isVisible).map((action) {
|
||||
// TODO TLAD [tv] togglers cf `_toIconActionButton`
|
||||
return ActionButton(
|
||||
text: action.getText(context),
|
||||
icon: action.getIcon(),
|
||||
enabled: canApply(action),
|
||||
onPressed: canApply(action) ? () => _onActionSelected(action) : null,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<Widget> _buildMobileActions({
|
||||
required AppMode appMode,
|
||||
required Selection<AvesEntry> selection,
|
||||
required bool Function(EntrySetAction action) isVisible,
|
||||
required bool Function(EntrySetAction action) canApply,
|
||||
}) {
|
||||
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 quickActionButtons = (isSelecting ? selectionQuickActions : browsingQuickActions).where(isVisible).map(
|
||||
(action) => _toActionButton(action, enabled: canApply(action), selection: selection),
|
||||
(action) => _toIconActionButton(action, enabled: canApply(action), selection: selection),
|
||||
);
|
||||
|
||||
return [
|
||||
|
@ -310,10 +388,10 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
),
|
||||
if (isSelecting && !device.isReadOnly && appMode == AppMode.main && !isTrash)
|
||||
PopupMenuItem<EntrySetAction>(
|
||||
enabled: canApplyEditActions,
|
||||
enabled: hasSelection,
|
||||
padding: EdgeInsets.zero,
|
||||
child: PopupMenuItemExpansionPanel<EntrySetAction>(
|
||||
enabled: canApplyEditActions,
|
||||
enabled: hasSelection,
|
||||
value: 'edit',
|
||||
icon: AIcons.edit,
|
||||
title: context.l10n.collectionActionEdit,
|
||||
|
@ -350,7 +428,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
// key is expected by test driver (e.g. 'menu-configureView', 'menu-map')
|
||||
Key _getActionKey(EntrySetAction action) => Key('menu-${action.name}');
|
||||
|
||||
Widget _toActionButton(EntrySetAction action, {required bool enabled, required Selection<AvesEntry> selection}) {
|
||||
Widget _toIconActionButton(EntrySetAction action, {required bool enabled, required Selection<AvesEntry> selection}) {
|
||||
final onPressed = enabled ? () => _onActionSelected(action) : null;
|
||||
switch (action) {
|
||||
case EntrySetAction.toggleTitleSearch:
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'package:provider/provider.dart';
|
|||
|
||||
class AvesAppBar extends StatelessWidget {
|
||||
final double contentHeight;
|
||||
final Widget leading;
|
||||
final Widget? leading;
|
||||
final Widget title;
|
||||
final List<Widget> actions;
|
||||
final Widget? bottom;
|
||||
|
@ -33,8 +33,8 @@ class AvesAppBar extends StatelessWidget {
|
|||
selector: (context, mq) => mq.padding.top,
|
||||
builder: (context, mqPaddingTop, child) {
|
||||
return SliverPersistentHeader(
|
||||
floating: true,
|
||||
pinned: device.isTelevision,
|
||||
floating: !device.isTelevision,
|
||||
pinned: false,
|
||||
delegate: _SliverAppBarDelegate(
|
||||
height: mqPaddingTop + appBarHeightForContentHeight(contentHeight),
|
||||
child: SafeArea(
|
||||
|
@ -51,15 +51,17 @@ class AvesAppBar extends StatelessWidget {
|
|||
height: kToolbarHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
leading != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Hero(
|
||||
tag: leadingHeroTag,
|
||||
flightShuttleBuilder: _flightShuttleBuilder,
|
||||
transitionOnUserGestures: true,
|
||||
child: leading,
|
||||
),
|
||||
child: leading!,
|
||||
),
|
||||
)
|
||||
: const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Hero(
|
||||
tag: titleHeroTag,
|
||||
|
|
|
@ -6,6 +6,7 @@ class ActionButton extends StatelessWidget {
|
|||
final String text;
|
||||
final Widget? icon;
|
||||
final bool enabled, showCaption;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
const ActionButton({
|
||||
super.key,
|
||||
|
@ -13,7 +14,8 @@ class ActionButton extends StatelessWidget {
|
|||
required this.icon,
|
||||
this.enabled = true,
|
||||
this.showCaption = true,
|
||||
});
|
||||
this.onPressed,
|
||||
}) : assert(onPressed == null || enabled);
|
||||
|
||||
static const int maxLines = 2;
|
||||
static const double padding = 8;
|
||||
|
@ -21,6 +23,7 @@ class ActionButton extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textStyle = _textStyle(context);
|
||||
final _enabled = onPressed != null || enabled;
|
||||
return SizedBox(
|
||||
width: _width(context),
|
||||
child: Column(
|
||||
|
@ -30,14 +33,14 @@ class ActionButton extends StatelessWidget {
|
|||
OverlayButton(
|
||||
child: IconButton(
|
||||
icon: icon ?? const SizedBox(),
|
||||
onPressed: enabled ? () {} : null,
|
||||
onPressed: onPressed ?? (_enabled ? () {} : null),
|
||||
),
|
||||
),
|
||||
if (showCaption) ...[
|
||||
const SizedBox(height: padding),
|
||||
Text(
|
||||
text,
|
||||
style: enabled ? textStyle : textStyle.copyWith(color: textStyle.color!.withOpacity(.2)),
|
||||
style: _enabled ? textStyle : textStyle.copyWith(color: textStyle.color!.withOpacity(.2)),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: maxLines,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:aves/model/device.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
|
@ -24,7 +25,7 @@ class ThumbnailsSection extends SettingsSection {
|
|||
|
||||
@override
|
||||
List<SettingsTile> tiles(BuildContext context) => [
|
||||
SettingsTileCollectionQuickActions(),
|
||||
if (!device.isTelevision) SettingsTileCollectionQuickActions(),
|
||||
SettingsTileThumbnailOverlay(),
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue