From 6c71752f18197673e6ff708424a32e8810a28dd1 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 13 Dec 2022 17:07:56 +0100 Subject: [PATCH] #437 tv: collection app bar --- lib/widgets/collection/app_bar.dart | 100 ++++++++++++++++-- lib/widgets/common/identity/aves_app_bar.dart | 26 ++--- .../common/quick_actions/action_button.dart | 9 +- .../settings/thumbnails/thumbnails.dart | 3 +- 4 files changed, 111 insertions(+), 27 deletions(-) diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index af839c247..eec20b726 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -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 with SingleTickerPr return Selector>( 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 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( onNotification: (notification) { @@ -198,12 +210,32 @@ class _CollectionAppBarState extends State with SingleTickerPr ); } - double get appBarContentHeight { - final hasQuery = context.read().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().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 with SingleTickerPr } List _buildActions(BuildContext context, Selection selection) { + final appMode = context.watch>().value; final isSelecting = selection.isSelecting; final selectedItemCount = selection.selectedItems.length; - final appMode = context.watch>().value; bool isVisible(EntrySetAction action) => _actionDelegate.isVisible( action, appMode: appMode, @@ -283,12 +315,58 @@ class _CollectionAppBarState extends State 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 _buildTelevisionActions({ + required AppMode appMode, + required Selection 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 _buildMobileActions({ + required AppMode appMode, + required Selection 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 with SingleTickerPr ), if (isSelecting && !device.isReadOnly && appMode == AppMode.main && !isTrash) PopupMenuItem( - enabled: canApplyEditActions, + enabled: hasSelection, padding: EdgeInsets.zero, child: PopupMenuItemExpansionPanel( - enabled: canApplyEditActions, + enabled: hasSelection, value: 'edit', icon: AIcons.edit, title: context.l10n.collectionActionEdit, @@ -350,7 +428,7 @@ class _CollectionAppBarState extends State 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 selection}) { + Widget _toIconActionButton(EntrySetAction action, {required bool enabled, required Selection selection}) { final onPressed = enabled ? () => _onActionSelected(action) : null; switch (action) { case EntrySetAction.toggleTitleSearch: diff --git a/lib/widgets/common/identity/aves_app_bar.dart b/lib/widgets/common/identity/aves_app_bar.dart index 8a4004aa2..299f8dd51 100644 --- a/lib/widgets/common/identity/aves_app_bar.dart +++ b/lib/widgets/common/identity/aves_app_bar.dart @@ -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 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( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Hero( - tag: leadingHeroTag, - flightShuttleBuilder: _flightShuttleBuilder, - transitionOnUserGestures: true, - child: leading, - ), - ), + leading != null + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Hero( + tag: leadingHeroTag, + flightShuttleBuilder: _flightShuttleBuilder, + transitionOnUserGestures: true, + child: leading!, + ), + ) + : const SizedBox(width: 16), Expanded( child: Hero( tag: titleHeroTag, diff --git a/lib/widgets/settings/common/quick_actions/action_button.dart b/lib/widgets/settings/common/quick_actions/action_button.dart index 2d0b2d92d..fc7962b3c 100644 --- a/lib/widgets/settings/common/quick_actions/action_button.dart +++ b/lib/widgets/settings/common/quick_actions/action_button.dart @@ -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, diff --git a/lib/widgets/settings/thumbnails/thumbnails.dart b/lib/widgets/settings/thumbnails/thumbnails.dart index fcf59343d..351a78052 100644 --- a/lib/widgets/settings/thumbnails/thumbnails.dart +++ b/lib/widgets/settings/thumbnails/thumbnails.dart @@ -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 tiles(BuildContext context) => [ - SettingsTileCollectionQuickActions(), + if (!device.isTelevision) SettingsTileCollectionQuickActions(), SettingsTileThumbnailOverlay(), ]; }