From da751190c5206e0cf8629fc4fe6af64e30ee857d Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 15 Dec 2022 18:59:20 +0100 Subject: [PATCH] #437 tv: toggle button state --- lib/model/actions/entry_actions.dart | 6 + lib/model/actions/map_cluster_actions.dart | 2 +- lib/model/actions/share_actions.dart | 7 +- lib/widgets/about/bug_report.dart | 2 +- lib/widgets/about/licenses.dart | 2 +- lib/widgets/collection/app_bar.dart | 45 ++- lib/widgets/collection/collection_grid.dart | 2 +- .../quick_choosers/album_chooser.dart | 2 +- .../quick_choosers/common/button.dart | 2 +- .../quick_choosers/common/menu.dart | 2 +- .../quick_choosers/common/quick_chooser.dart | 0 .../quick_choosers/common/route_layout.dart | 0 .../quick_choosers/move_button.dart | 6 +- .../quick_choosers/rate_button.dart | 4 +- .../quick_choosers/rate_chooser.dart | 2 +- .../quick_choosers/share_button.dart | 4 +- .../quick_choosers/share_chooser.dart | 2 +- .../quick_choosers/tag_button.dart | 6 +- .../quick_choosers/tag_chooser.dart | 2 +- .../togglers/favourite.dart} | 59 ++++ .../action_controls/togglers/mute.dart} | 32 ++ .../action_controls/togglers/play.dart} | 27 ++ .../togglers/title_search.dart} | 26 ++ .../identity/buttons/captioned_button.dart | 105 ++++++ .../outlined_button.dart} | 0 .../identity/buttons/overlay_button.dart} | 0 lib/widgets/common/map/geo_map.dart | 2 +- .../entry_editors/rename_entry_set_page.dart | 2 +- .../pick_dialogs/location_pick_page.dart | 2 +- lib/widgets/filter_grids/common/app_bar.dart | 38 +- .../common/quick_actions/action_button.dart | 78 ----- .../quick_actions/available_actions.dart | 14 +- .../common/quick_actions/editor_page.dart | 15 +- .../common/quick_actions/quick_actions.dart | 2 +- .../settings/home_widget_settings_page.dart | 2 +- .../navigation/drawer_tab_albums.dart | 2 +- .../privacy/file_picker/file_picker_page.dart | 2 +- .../settings/privacy/hidden_items_page.dart | 2 +- lib/widgets/viewer/info/basic_section.dart | 4 +- lib/widgets/viewer/overlay/panorama.dart | 2 +- .../viewer/overlay/slideshow_buttons.dart | 4 +- .../viewer/overlay/video/controls.dart | 4 +- lib/widgets/viewer/overlay/video/video.dart | 2 +- .../viewer/overlay/viewer_buttons.dart | 329 +++++++++++------- .../viewer/overlay/wallpaper_buttons.dart | 2 +- lib/widgets/viewer/panorama_page.dart | 2 +- lib/widgets/welcome_page.dart | 2 +- 47 files changed, 570 insertions(+), 289 deletions(-) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/album_chooser.dart (93%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/common/button.dart (97%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/common/menu.dart (98%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/common/quick_chooser.dart (100%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/common/route_layout.dart (100%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/move_button.dart (90%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/rate_button.dart (86%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/rate_chooser.dart (96%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/share_button.dart (90%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/share_chooser.dart (94%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/tag_button.dart (89%) rename lib/widgets/common/{app_bar => action_controls}/quick_choosers/tag_chooser.dart (92%) rename lib/widgets/common/{app_bar/favourite_toggler.dart => action_controls/togglers/favourite.dart} (65%) rename lib/widgets/{viewer/overlay/video/mute_toggler.dart => common/action_controls/togglers/mute.dart} (61%) rename lib/widgets/{viewer/overlay/video/play_toggler.dart => common/action_controls/togglers/play.dart} (79%) rename lib/widgets/common/{app_bar/title_search_toggler.dart => action_controls/togglers/title_search.dart} (54%) create mode 100644 lib/widgets/common/identity/buttons/captioned_button.dart rename lib/widgets/common/identity/{buttons.dart => buttons/outlined_button.dart} (100%) rename lib/widgets/{viewer/overlay/common.dart => common/identity/buttons/overlay_button.dart} (100%) delete mode 100644 lib/widgets/settings/common/quick_actions/action_button.dart diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart index a03fa7fc7..0a653b736 100644 --- a/lib/model/actions/entry_actions.dart +++ b/lib/model/actions/entry_actions.dart @@ -114,6 +114,12 @@ class EntryActions { EntryAction.videoSettings, ]; + static const videoPlayback = [ + EntryAction.videoReplay10, + EntryAction.videoTogglePlay, + EntryAction.videoSkip10, + ]; + static const commonMetadataActions = [ EntryAction.editDate, EntryAction.editLocation, diff --git a/lib/model/actions/map_cluster_actions.dart b/lib/model/actions/map_cluster_actions.dart index 8301767fc..0f0dd0a63 100644 --- a/lib/model/actions/map_cluster_actions.dart +++ b/lib/model/actions/map_cluster_actions.dart @@ -27,4 +27,4 @@ extension ExtraMapClusterAction on MapClusterAction { return AIcons.clear; } } -} \ No newline at end of file +} diff --git a/lib/model/actions/share_actions.dart b/lib/model/actions/share_actions.dart index bd3480e64..74c7bec8a 100644 --- a/lib/model/actions/share_actions.dart +++ b/lib/model/actions/share_actions.dart @@ -2,7 +2,10 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/widgets.dart'; -enum ShareAction { imageOnly, videoOnly, } +enum ShareAction { + imageOnly, + videoOnly, +} extension ExtraShareAction on ShareAction { String getText(BuildContext context) { @@ -24,4 +27,4 @@ extension ExtraShareAction on ShareAction { return AIcons.video; } } -} \ No newline at end of file +} diff --git a/lib/widgets/about/bug_report.dart b/lib/widgets/about/bug_report.dart index a4be8e376..83a959659 100644 --- a/lib/widgets/about/bug_report.dart +++ b/lib/widgets/about/bug_report.dart @@ -15,7 +15,7 @@ import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/common/action_mixins/feedback.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/identity/buttons.dart'; +import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/widgets/about/licenses.dart b/lib/widgets/about/licenses.dart index d41ed9d88..08088bfdd 100644 --- a/lib/widgets/about/licenses.dart +++ b/lib/widgets/about/licenses.dart @@ -5,7 +5,7 @@ import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/basic/link_chip.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; -import 'package:aves/widgets/common/identity/buttons.dart'; +import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index f0dc33d06..3d2fbeaad 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -21,18 +21,18 @@ import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/entry_set_action_delegate.dart'; import 'package:aves/widgets/collection/filter_bar.dart'; import 'package:aves/widgets/collection/query_bar.dart'; +import 'package:aves/widgets/common/action_controls/togglers/favourite.dart'; +import 'package:aves/widgets/common/action_controls/togglers/title_search.dart'; import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart'; import 'package:aves/widgets/common/app_bar/app_bar_title.dart'; -import 'package:aves/widgets/common/app_bar/favourite_toggler.dart'; -import 'package:aves/widgets/common/app_bar/title_search_toggler.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_app_bar.dart'; +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; 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'; @@ -176,7 +176,7 @@ class _CollectionAppBarState extends State with SingleTickerPr children: [ if (isTelevision) SizedBox( - height: ActionButton.getTelevisionButtonHeight(context), + height: CaptionedButton.getTelevisionButtonHeight(context), child: ListView( padding: const EdgeInsets.symmetric(horizontal: 8), scrollDirection: Axis.horizontal, @@ -215,7 +215,7 @@ class _CollectionAppBarState extends State with SingleTickerPr double get appBarContentHeight { double height = kToolbarHeight; if (device.isTelevision) { - height += ActionButton.getTelevisionButtonHeight(context); + height += CaptionedButton.getTelevisionButtonHeight(context); } if (showFilterBar) { height += FilterBar.preferredHeight; @@ -339,12 +339,10 @@ class _CollectionAppBarState extends State with SingleTickerPr ...EntrySetActions.general, ...isSelecting ? EntrySetActions.pageSelection : EntrySetActions.pageBrowsing, ].where(isVisible).map((action) { - // TODO TLAD [tv] togglers cf `_toIconActionButton` final enabled = canApply(action); - return ActionButton( - text: action.getText(context), - icon: action.getIcon(), - enabled: enabled, + return CaptionedButton( + iconButton: _buildButtonIcon(context, action, enabled: enabled, selection: selection), + captionText: _buildButtonCaption(context, action, enabled: enabled), onPressed: enabled ? () => _onActionSelected(action) : null, ); }).toList(); @@ -364,7 +362,7 @@ class _CollectionAppBarState extends State with SingleTickerPr final browsingQuickActions = settings.collectionBrowsingQuickActions; final selectionQuickActions = isTrash ? [EntrySetAction.delete, EntrySetAction.restore] : settings.collectionSelectionQuickActions; final quickActionButtons = (isSelecting ? selectionQuickActions : browsingQuickActions).where(isVisible).map( - (action) => _toIconActionButton(action, enabled: canApply(action), selection: selection), + (action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection), ); return [ @@ -426,7 +424,12 @@ 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 _toIconActionButton(EntrySetAction action, {required bool enabled, required Selection selection}) { + Widget _buildButtonIcon( + BuildContext context, + EntrySetAction action, { + required bool enabled, + required Selection selection, + }) { final onPressed = enabled ? () => _onActionSelected(action) : null; switch (action) { case EntrySetAction.toggleTitleSearch: @@ -455,6 +458,24 @@ class _CollectionAppBarState extends State with SingleTickerPr } } + Widget _buildButtonCaption( + BuildContext context, + EntrySetAction action, { + required bool enabled, + }) { + switch (action) { + case EntrySetAction.toggleTitleSearch: + return TitleSearchTogglerCaption( + enabled: enabled, + ); + default: + return CaptionedButtonText( + text: action.getText(context), + enabled: enabled, + ); + } + } + PopupMenuItem _toMenuItem(EntrySetAction action, {required bool enabled, required Selection selection}) { late Widget child; switch (action) { diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 7ff185e0d..b17084bc9 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -33,7 +33,7 @@ import 'package:aves/widgets/common/grid/sections/section_layout.dart'; import 'package:aves/widgets/common/grid/selector.dart'; import 'package:aves/widgets/common/grid/sliver.dart'; import 'package:aves/widgets/common/grid/theme.dart'; -import 'package:aves/widgets/common/identity/buttons.dart'; +import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/identity/scroll_thumb.dart'; import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart'; diff --git a/lib/widgets/common/app_bar/quick_choosers/album_chooser.dart b/lib/widgets/common/action_controls/quick_choosers/album_chooser.dart similarity index 93% rename from lib/widgets/common/app_bar/quick_choosers/album_chooser.dart rename to lib/widgets/common/action_controls/quick_choosers/album_chooser.dart index 90658ff4f..73e87d8dc 100644 --- a/lib/widgets/common/app_bar/quick_choosers/album_chooser.dart +++ b/lib/widgets/common/action_controls/quick_choosers/album_chooser.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/menu.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/common/app_bar/quick_choosers/common/button.dart b/lib/widgets/common/action_controls/quick_choosers/common/button.dart similarity index 97% rename from lib/widgets/common/app_bar/quick_choosers/common/button.dart rename to lib/widgets/common/action_controls/quick_choosers/common/button.dart index 2d1cf1603..d83f728ad 100644 --- a/lib/widgets/common/app_bar/quick_choosers/common/button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/common/button.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/theme/durations.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/route_layout.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/route_layout.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/common/app_bar/quick_choosers/common/menu.dart b/lib/widgets/common/action_controls/quick_choosers/common/menu.dart similarity index 98% rename from lib/widgets/common/app_bar/quick_choosers/common/menu.dart rename to lib/widgets/common/action_controls/quick_choosers/common/menu.dart index cef81ee4f..5beae8e82 100644 --- a/lib/widgets/common/app_bar/quick_choosers/common/menu.dart +++ b/lib/widgets/common/action_controls/quick_choosers/common/menu.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:ui'; import 'package:aves/theme/durations.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/quick_chooser.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/quick_chooser.dart'; import 'package:aves_ui/aves_ui.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/app_bar/quick_choosers/common/quick_chooser.dart b/lib/widgets/common/action_controls/quick_choosers/common/quick_chooser.dart similarity index 100% rename from lib/widgets/common/app_bar/quick_choosers/common/quick_chooser.dart rename to lib/widgets/common/action_controls/quick_choosers/common/quick_chooser.dart diff --git a/lib/widgets/common/app_bar/quick_choosers/common/route_layout.dart b/lib/widgets/common/action_controls/quick_choosers/common/route_layout.dart similarity index 100% rename from lib/widgets/common/app_bar/quick_choosers/common/route_layout.dart rename to lib/widgets/common/action_controls/quick_choosers/common/route_layout.dart diff --git a/lib/widgets/common/app_bar/quick_choosers/move_button.dart b/lib/widgets/common/action_controls/quick_choosers/move_button.dart similarity index 90% rename from lib/widgets/common/app_bar/quick_choosers/move_button.dart rename to lib/widgets/common/action_controls/quick_choosers/move_button.dart index 5a2a47768..9589524f8 100644 --- a/lib/widgets/common/app_bar/quick_choosers/move_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/move_button.dart @@ -3,9 +3,9 @@ import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/album_chooser.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/button.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/menu.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/album_chooser.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart'; import 'package:collection/collection.dart'; diff --git a/lib/widgets/common/app_bar/quick_choosers/rate_button.dart b/lib/widgets/common/action_controls/quick_choosers/rate_button.dart similarity index 86% rename from lib/widgets/common/app_bar/quick_choosers/rate_button.dart rename to lib/widgets/common/action_controls/quick_choosers/rate_button.dart index 9d51218f6..cb3882d75 100644 --- a/lib/widgets/common/app_bar/quick_choosers/rate_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/rate_button.dart @@ -1,6 +1,6 @@ import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/button.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/rate_chooser.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/rate_chooser.dart'; import 'package:flutter/material.dart'; class RateButton extends ChooserQuickButton { diff --git a/lib/widgets/common/app_bar/quick_choosers/rate_chooser.dart b/lib/widgets/common/action_controls/quick_choosers/rate_chooser.dart similarity index 96% rename from lib/widgets/common/app_bar/quick_choosers/rate_chooser.dart rename to lib/widgets/common/action_controls/quick_choosers/rate_chooser.dart index d28aa90fe..b028f680a 100644 --- a/lib/widgets/common/app_bar/quick_choosers/rate_chooser.dart +++ b/lib/widgets/common/action_controls/quick_choosers/rate_chooser.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/theme/icons.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/quick_chooser.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/quick_chooser.dart'; import 'package:flutter/material.dart'; class RateQuickChooser extends StatefulWidget { diff --git a/lib/widgets/common/app_bar/quick_choosers/share_button.dart b/lib/widgets/common/action_controls/quick_choosers/share_button.dart similarity index 90% rename from lib/widgets/common/app_bar/quick_choosers/share_button.dart rename to lib/widgets/common/action_controls/quick_choosers/share_button.dart index 18218d5a1..d4b234071 100644 --- a/lib/widgets/common/app_bar/quick_choosers/share_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/share_button.dart @@ -1,8 +1,8 @@ import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/share_actions.dart'; import 'package:aves/model/entry.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/button.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/share_chooser.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/share_chooser.dart'; import 'package:flutter/material.dart'; class ShareButton extends ChooserQuickButton { diff --git a/lib/widgets/common/app_bar/quick_choosers/share_chooser.dart b/lib/widgets/common/action_controls/quick_choosers/share_chooser.dart similarity index 94% rename from lib/widgets/common/app_bar/quick_choosers/share_chooser.dart rename to lib/widgets/common/action_controls/quick_choosers/share_chooser.dart index 47b9da0ab..3600877e6 100644 --- a/lib/widgets/common/app_bar/quick_choosers/share_chooser.dart +++ b/lib/widgets/common/action_controls/quick_choosers/share_chooser.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/model/actions/share_actions.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/menu.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/app_bar/quick_choosers/tag_button.dart b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart similarity index 89% rename from lib/widgets/common/app_bar/quick_choosers/tag_button.dart rename to lib/widgets/common/action_controls/quick_choosers/tag_button.dart index 1360d44ef..cb048e3e6 100644 --- a/lib/widgets/common/app_bar/quick_choosers/tag_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart @@ -3,9 +3,9 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/button.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/menu.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/tag_chooser.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/tag_chooser.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart'; import 'package:collection/collection.dart'; diff --git a/lib/widgets/common/app_bar/quick_choosers/tag_chooser.dart b/lib/widgets/common/action_controls/quick_choosers/tag_chooser.dart similarity index 92% rename from lib/widgets/common/app_bar/quick_choosers/tag_chooser.dart rename to lib/widgets/common/action_controls/quick_choosers/tag_chooser.dart index 358faead3..fca80cea3 100644 --- a/lib/widgets/common/app_bar/quick_choosers/tag_chooser.dart +++ b/lib/widgets/common/action_controls/quick_choosers/tag_chooser.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/widgets/common/app_bar/quick_choosers/common/menu.dart'; +import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/app_bar/favourite_toggler.dart b/lib/widgets/common/action_controls/togglers/favourite.dart similarity index 65% rename from lib/widgets/common/app_bar/favourite_toggler.dart rename to lib/widgets/common/action_controls/togglers/favourite.dart index f21d9325a..cfa4d9b4f 100644 --- a/lib/widgets/common/app_bar/favourite_toggler.dart +++ b/lib/widgets/common/action_controls/togglers/favourite.dart @@ -5,6 +5,7 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/fx/sweeper.dart'; +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -49,6 +50,7 @@ class _FavouriteTogglerState extends State { @override void dispose() { favourites.removeListener(_onChanged); + isFavouriteNotifier.dispose(); super.dispose(); } @@ -94,3 +96,60 @@ class _FavouriteTogglerState extends State { isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite); } } + +class FavouriteTogglerCaption extends StatefulWidget { + final Set entries; + final bool enabled; + + const FavouriteTogglerCaption({ + super.key, + required this.entries, + required this.enabled, + }); + + @override + State createState() => _FavouriteTogglerCaptionState(); +} + +class _FavouriteTogglerCaptionState extends State { + final ValueNotifier isFavouriteNotifier = ValueNotifier(false); + + Set get entries => widget.entries; + + @override + void initState() { + super.initState(); + favourites.addListener(_onChanged); + _onChanged(); + } + + @override + void didUpdateWidget(covariant FavouriteTogglerCaption oldWidget) { + super.didUpdateWidget(oldWidget); + _onChanged(); + } + + @override + void dispose() { + favourites.removeListener(_onChanged); + isFavouriteNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: isFavouriteNotifier, + builder: (context, isFavourite, child) { + return CaptionedButtonText( + text: isFavourite ? context.l10n.entryActionRemoveFavourite : context.l10n.entryActionAddFavourite, + enabled: widget.enabled, + ); + }, + ); + } + + void _onChanged() { + isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite); + } +} diff --git a/lib/widgets/viewer/overlay/video/mute_toggler.dart b/lib/widgets/common/action_controls/togglers/mute.dart similarity index 61% rename from lib/widgets/viewer/overlay/video/mute_toggler.dart rename to lib/widgets/common/action_controls/togglers/mute.dart index 5b7a09350..94c691f8c 100644 --- a/lib/widgets/viewer/overlay/video/mute_toggler.dart +++ b/lib/widgets/common/action_controls/togglers/mute.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:flutter/material.dart'; @@ -47,3 +48,34 @@ class MuteToggler extends StatelessWidget { ); } } + +class MuteTogglerCaption extends StatelessWidget { + final AvesVideoController? controller; + final bool enabled; + + const MuteTogglerCaption({ + super.key, + required this.controller, + required this.enabled, + }); + + bool get isMuted => controller?.isMuted ?? false; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: controller?.canMuteNotifier ?? ValueNotifier(false), + builder: (context, canDo, child) { + return StreamBuilder( + stream: controller?.volumeStream ?? Stream.value(1.0), + builder: (context, snapshot) { + return CaptionedButtonText( + text: isMuted ? context.l10n.videoActionUnmute : context.l10n.videoActionMute, + enabled: canDo && enabled, + ); + }, + ); + }, + ); + } +} diff --git a/lib/widgets/viewer/overlay/video/play_toggler.dart b/lib/widgets/common/action_controls/togglers/play.dart similarity index 79% rename from lib/widgets/viewer/overlay/video/play_toggler.dart rename to lib/widgets/common/action_controls/togglers/play.dart index 27599cdce..814958b2b 100644 --- a/lib/widgets/viewer/overlay/video/play_toggler.dart +++ b/lib/widgets/common/action_controls/togglers/play.dart @@ -4,6 +4,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -98,3 +99,29 @@ class _PlayTogglerState extends State with SingleTickerProviderStat } } } + +class PlayTogglerCaption extends StatelessWidget { + final AvesVideoController? controller; + final bool enabled; + + const PlayTogglerCaption({ + super.key, + required this.controller, + required this.enabled, + }); + + bool get isPlaying => controller?.isPlaying ?? false; + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: controller?.statusStream ?? Stream.value(VideoStatus.idle), + builder: (context, snapshot) { + return CaptionedButtonText( + text: isPlaying ? context.l10n.videoActionPause : context.l10n.videoActionPlay, + enabled: enabled, + ); + }, + ); + } +} diff --git a/lib/widgets/common/app_bar/title_search_toggler.dart b/lib/widgets/common/action_controls/togglers/title_search.dart similarity index 54% rename from lib/widgets/common/app_bar/title_search_toggler.dart rename to lib/widgets/common/action_controls/togglers/title_search.dart index 52e7c7bff..3b3d9ae55 100644 --- a/lib/widgets/common/app_bar/title_search_toggler.dart +++ b/lib/widgets/common/action_controls/togglers/title_search.dart @@ -1,7 +1,10 @@ +import 'package:aves/model/query.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class TitleSearchToggler extends StatelessWidget { final bool queryEnabled, isMenuItem; @@ -30,3 +33,26 @@ class TitleSearchToggler extends StatelessWidget { ); } } + +class TitleSearchTogglerCaption extends StatelessWidget { + final bool enabled; + + const TitleSearchTogglerCaption({ + super.key, + required this.enabled, + }); + + @override + Widget build(BuildContext context) { + // `Query` may not be available during hero + return Selector( + selector: (context, query) => query?.enabled ?? false, + builder: (context, queryEnabled, child) { + return CaptionedButtonText( + text: queryEnabled ? context.l10n.collectionActionHideTitleSearch : context.l10n.collectionActionShowTitleSearch, + enabled: enabled, + ); + }, + ); + } +} diff --git a/lib/widgets/common/identity/buttons/captioned_button.dart b/lib/widgets/common/identity/buttons/captioned_button.dart new file mode 100644 index 000000000..6c8e01ada --- /dev/null +++ b/lib/widgets/common/identity/buttons/captioned_button.dart @@ -0,0 +1,105 @@ +import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +class CaptionedButton extends StatelessWidget { + final Animation scale; + final Widget captionText; + final Widget iconButton; + final bool showCaption; + final VoidCallback? onPressed; + + CaptionedButton({ + super.key, + this.scale = kAlwaysCompleteAnimation, + Widget? icon, + Widget? iconButton, + String? caption, + Widget? captionText, + this.showCaption = true, + required this.onPressed, + }) : assert(icon != null || iconButton != null), + assert(caption != null || captionText != null), + iconButton = iconButton ?? IconButton(icon: icon!, onPressed: onPressed), + captionText = captionText ?? CaptionedButtonText(text: caption!, enabled: onPressed != null); + + static const double padding = 8; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: _width(context), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: padding), + OverlayButton( + scale: scale, + child: iconButton, + ), + if (showCaption) ...[ + const SizedBox(height: padding), + ScaleTransition( + scale: scale, + child: captionText, + ), + ], + const SizedBox(height: padding), + ], + ), + ); + } + + static double _width(BuildContext context) => OverlayButton.getSize(context) + padding * 2; + + static Size getSize(BuildContext context, String text, {required bool showCaption}) { + final width = _width(context); + var height = width; + if (showCaption) { + final para = RenderParagraph( + TextSpan(text: text, style: CaptionedButtonText.textStyle(context)), + textDirection: TextDirection.ltr, + textScaleFactor: MediaQuery.textScaleFactorOf(context), + maxLines: CaptionedButtonText.maxLines, + )..layout(const BoxConstraints(), parentUsesSize: true); + height += para.getMaxIntrinsicHeight(width) + padding; + } + return Size(width, height); + } + + static double getTelevisionButtonHeight(BuildContext context) { + final text = 'whatever' * 42; + return CaptionedButton.getSize(context, text, showCaption: true).height; + } +} + +class CaptionedButtonText extends StatelessWidget { + final String text; + final bool enabled; + + static const int maxLines = 2; + + const CaptionedButtonText({ + super.key, + required this.text, + required this.enabled, + }); + + @override + Widget build(BuildContext context) { + var style = textStyle(context); + if (!enabled) { + style = style.copyWith(color: style.color!.withOpacity(.2)); + } + + return Text( + text, + style: style, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: maxLines, + ); + } + + static TextStyle textStyle(BuildContext context) => Theme.of(context).textTheme.bodySmall!; +} diff --git a/lib/widgets/common/identity/buttons.dart b/lib/widgets/common/identity/buttons/outlined_button.dart similarity index 100% rename from lib/widgets/common/identity/buttons.dart rename to lib/widgets/common/identity/buttons/outlined_button.dart diff --git a/lib/widgets/viewer/overlay/common.dart b/lib/widgets/common/identity/buttons/overlay_button.dart similarity index 100% rename from lib/widgets/viewer/overlay/common.dart rename to lib/widgets/common/identity/buttons/overlay_button.dart diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index f763b68b3..796b30179 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -13,13 +13,13 @@ import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/utils/math_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/common/map/attribution.dart'; import 'package:aves/widgets/common/map/buttons/panel.dart'; import 'package:aves/widgets/common/map/decorator.dart'; import 'package:aves/widgets/common/map/leaflet/map.dart'; import 'package:aves/widgets/common/thumbnail/image.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; -import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves_map/aves_map.dart'; import 'package:collection/collection.dart'; import 'package:fluster/fluster.dart'; diff --git a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart index 43b29de6e..934bcd1cc 100644 --- a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart +++ b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart @@ -10,7 +10,7 @@ import 'package:aves/widgets/collection/collection_grid.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/grid/theme.dart'; -import 'package:aves/widgets/common/identity/buttons.dart'; +import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/common/thumbnail/decorated.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; diff --git a/lib/widgets/dialogs/pick_dialogs/location_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/location_pick_page.dart index 6f2a1e4d1..930c55bd3 100644 --- a/lib/widgets/dialogs/pick_dialogs/location_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/location_pick_page.dart @@ -11,7 +11,7 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/utils/debouncer.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/common/identity/buttons.dart'; +import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/common/map/geo_map.dart'; import 'package:aves/widgets/common/providers/map_theme_provider.dart'; import 'package:aves_map/aves_map.dart'; diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index 67d717e1a..e95b110c9 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -8,17 +8,17 @@ import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/common/action_controls/togglers/title_search.dart'; import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart'; import 'package:aves/widgets/common/app_bar/app_bar_title.dart'; -import 'package:aves/widgets/common/app_bar/title_search_toggler.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_app_bar.dart'; +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves/widgets/filter_grids/common/query_bar.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'; @@ -143,7 +143,7 @@ class _FilterGridAppBarState().enabled) { height += FilterQueryBar.preferredHeight; @@ -281,12 +281,10 @@ class _FilterGridAppBarState _onActionSelected(context, action, actionDelegate) : null, ); }).toList(); @@ -302,7 +300,7 @@ class _FilterGridAppBarState _toActionButton(context, actionDelegate, action, enabled: canApply(action)), + (action) => _buildButtonIcon(context, actionDelegate, action, enabled: canApply(action)), ); return [ @@ -342,7 +340,7 @@ class _FilterGridAppBarState>>().isSelecting) { _browseToSelectAnimation.forward(); diff --git a/lib/widgets/settings/common/quick_actions/action_button.dart b/lib/widgets/settings/common/quick_actions/action_button.dart deleted file mode 100644 index 49ffc0a48..000000000 --- a/lib/widgets/settings/common/quick_actions/action_button.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:aves/widgets/viewer/overlay/common.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; - -class ActionButton extends StatelessWidget { - final String text; - final Widget? icon; - final bool enabled, showCaption; - final VoidCallback? onPressed; - - const ActionButton({ - super.key, - required this.text, - 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; - - @override - Widget build(BuildContext context) { - final textStyle = _textStyle(context); - final _enabled = onPressed != null || enabled; - return SizedBox( - width: _width(context), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: padding), - OverlayButton( - child: IconButton( - icon: icon ?? const SizedBox(), - onPressed: onPressed ?? (_enabled ? () {} : null), - ), - ), - if (showCaption) ...[ - const SizedBox(height: padding), - Text( - text, - style: _enabled ? textStyle : textStyle.copyWith(color: textStyle.color!.withOpacity(.2)), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: maxLines, - ), - ], - const SizedBox(height: padding), - ], - ), - ); - } - - static TextStyle _textStyle(BuildContext context) => Theme.of(context).textTheme.bodySmall!; - - static double _width(BuildContext context) => OverlayButton.getSize(context) + padding * 2; - - static Size getSize(BuildContext context, String text, {required bool showCaption}) { - final width = _width(context); - var height = width; - if (showCaption) { - final para = RenderParagraph( - TextSpan(text: text, style: _textStyle(context)), - textDirection: TextDirection.ltr, - textScaleFactor: MediaQuery.textScaleFactorOf(context), - maxLines: maxLines, - )..layout(const BoxConstraints(), parentUsesSize: true); - height += para.getMaxIntrinsicHeight(width) + padding; - } - return Size(width, height); - } - - static double getTelevisionButtonHeight(BuildContext context) { - final text = 'whatever' * 42; - return ActionButton.getSize(context, text, showCaption: true).height; - } -} diff --git a/lib/widgets/settings/common/quick_actions/available_actions.dart b/lib/widgets/settings/common/quick_actions/available_actions.dart index 9d5112d99..9feb52162 100644 --- a/lib/widgets/settings/common/quick_actions/available_actions.dart +++ b/lib/widgets/settings/common/quick_actions/available_actions.dart @@ -1,7 +1,7 @@ +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; +import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; -import 'package:aves/widgets/settings/common/quick_actions/action_button.dart'; import 'package:aves/widgets/settings/common/quick_actions/placeholder.dart'; -import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -13,7 +13,7 @@ class AvailableActionPanel extends StatelessWidget { final ValueNotifier draggedQuickAction; final ValueNotifier draggedAvailableAction; final bool Function(T? action) removeQuickAction; - final Widget? Function(T action) actionIcon; + final Widget Function(T action) actionIcon; final String Function(BuildContext context, T action) actionText; static const double spacing = 8; @@ -107,11 +107,11 @@ class AvailableActionPanel extends StatelessWidget { bool enabled = true, bool showCaption = true, }) => - ActionButton( - text: actionText(context, action), + CaptionedButton( icon: actionIcon(action), - enabled: enabled, + caption: actionText(context, action), showCaption: showCaption, + onPressed: enabled ? () {} : null, ); void _setDraggedQuickAction(T? action) => draggedQuickAction.value = action; @@ -121,7 +121,7 @@ class AvailableActionPanel extends StatelessWidget { void _setPanelHighlight(bool flag) => panelHighlight.value = flag; static double heightFor(BuildContext context, List captions, double width) { - final buttonSizes = captions.map((v) => ActionButton.getSize(context, v, showCaption: true)); + final buttonSizes = captions.map((v) => CaptionedButton.getSize(context, v, showCaption: true)); final actionsPerRun = (width - padding.horizontal + spacing) ~/ (buttonSizes.first.width + spacing); final runCount = (captions.length / actionsPerRun).ceil(); var height = .0; diff --git a/lib/widgets/settings/common/quick_actions/editor_page.dart b/lib/widgets/settings/common/quick_actions/editor_page.dart index 62e0d9ed6..c2b238d69 100644 --- a/lib/widgets/settings/common/quick_actions/editor_page.dart +++ b/lib/widgets/settings/common/quick_actions/editor_page.dart @@ -5,12 +5,12 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/settings/common/quick_actions/action_button.dart'; +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; +import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/settings/common/quick_actions/action_panel.dart'; import 'package:aves/widgets/settings/common/quick_actions/available_actions.dart'; import 'package:aves/widgets/settings/common/quick_actions/placeholder.dart'; import 'package:aves/widgets/settings/common/quick_actions/quick_actions.dart'; -import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -19,7 +19,7 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart'; class QuickActionEditorPage extends StatelessWidget { final String title, bannerText; final List> allAvailableActions; - final Widget? Function(T action) actionIcon; + final Widget Function(T action) actionIcon; final String Function(BuildContext context, T action) actionText; final List Function() load; final void Function(List actions) save; @@ -58,7 +58,7 @@ class QuickActionEditorPage extends StatelessWidget { class QuickActionEditorBody extends StatefulWidget { final String bannerText; final List> allAvailableActions; - final Widget? Function(T action) actionIcon; + final Widget Function(T action) actionIcon; final String Function(BuildContext context, T action) actionText; final List Function() load; final void Function(List actions) save; @@ -208,10 +208,11 @@ class _QuickActionEditorBodyState extends State ActionButton( - text: widget.actionText(context, action), + draggableFeedbackBuilder: (action) => CaptionedButton( icon: widget.actionIcon(action), + caption: widget.actionText(context, action), showCaption: false, + onPressed: () {}, ), child: _buildQuickActionButton(action, animation), ); @@ -361,7 +362,7 @@ class _QuickActionEditorBodyState extends State !quickActions.contains(action)).where(actionDelegate.isVisible).toList(); final videoActions = EntryActions.video.where((action) => !quickActions.contains(action)).where(actionDelegate.isVisible).toList(); return ViewerButtonRowContent( + actionDelegate: EntryActionDelegate(mainEntry, pageEntry, collection), quickActions: quickActions, topLevelActions: topLevelActions, exportActions: exportActions, @@ -82,7 +84,6 @@ class ViewerButtons extends StatelessWidget { scale: scale, mainEntry: mainEntry, pageEntry: pageEntry, - collection: collection, ); }, ); @@ -93,48 +94,64 @@ class ViewerButtons extends StatelessWidget { } class _TvButtonRowContent extends StatelessWidget { + final EntryActionDelegate actionDelegate; final Animation scale; final AvesEntry mainEntry, pageEntry; - final CollectionLens? collection; const _TvButtonRowContent({ + required this.actionDelegate, required this.scale, required this.mainEntry, required this.pageEntry, - required this.collection, }); @override Widget build(BuildContext context) { - final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection); - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...EntryActions.topLevel, - ...EntryActions.export, - ...EntryActions.video, - ].where(actionDelegate.isVisible).map((action) { - // TODO TLAD [tv] togglers cf `_buildOverlayButton` - // TODO TLAD [tv] use `scale` - final enabled = actionDelegate.canApply(action); - return ActionButton( - text: action.getText(context), - icon: action.getIcon(), - enabled: enabled, - onPressed: enabled ? () => actionDelegate.onActionSelected(context, action) : null, + return Selector( + selector: (context, vc) => vc.getController(pageEntry), + builder: (context, videoController, child) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...EntryActions.topLevel, + ...EntryActions.export, + ...EntryActions.videoPlayback, + ...EntryActions.video, + ].where(actionDelegate.isVisible).map((action) { + final enabled = actionDelegate.canApply(action); + return CaptionedButton( + scale: scale, + iconButton: _buildButtonIcon( + context: context, + action: action, + mainEntry: mainEntry, + pageEntry: pageEntry, + videoController: videoController, + actionDelegate: actionDelegate, + ), + captionText: _buildButtonCaption( + context: context, + action: action, + mainEntry: mainEntry, + pageEntry: pageEntry, + videoController: videoController, + enabled: enabled, + ), + onPressed: enabled ? () => actionDelegate.onActionSelected(context, action) : null, + ); + }).toList(), ); - }).toList(), + }, ); } } class ViewerButtonRowContent extends StatelessWidget { + final EntryActionDelegate actionDelegate; final List quickActions, topLevelActions, exportActions, videoActions; final Animation scale; final AvesEntry mainEntry, pageEntry; - final CollectionLens? collection; final ValueNotifier _popupExpandedNotifier = ValueNotifier(null); AvesEntry get favouriteTargetEntry => mainEntry.isBurst ? pageEntry : mainEntry; @@ -143,6 +160,7 @@ class ViewerButtonRowContent extends StatelessWidget { ViewerButtonRowContent({ super.key, + required this.actionDelegate, required this.quickActions, required this.topLevelActions, required this.exportActions, @@ -150,7 +168,6 @@ class ViewerButtonRowContent extends StatelessWidget { required this.scale, required this.mainEntry, required this.pageEntry, - required this.collection, }); @override @@ -216,7 +233,7 @@ class ViewerButtonRowContent extends StatelessWidget { onSelected: (action) { _popupExpandedNotifier.value = null; // wait for the popup menu to hide before proceeding with the action - Future.delayed(Durations.popupMenuAnimation * timeDilation, () => _onActionSelected(context, action)); + Future.delayed(Durations.popupMenuAnimation * timeDilation, () => actionDelegate.onActionSelected(context, action)); }, onCanceled: () { _popupExpandedNotifier.value = null; @@ -239,101 +256,18 @@ class ViewerButtonRowContent extends StatelessWidget { } Widget _buildOverlayButton(BuildContext context, EntryAction action, AvesVideoController? videoController) { - Widget? child; - void onPressed() => _onActionSelected(context, action); - - ValueListenableBuilder _buildFromListenable(ValueListenable? enabledNotifier) { - return ValueListenableBuilder( - valueListenable: enabledNotifier ?? ValueNotifier(false), - builder: (context, canDo, child) => IconButton( - icon: child!, - onPressed: canDo ? onPressed : null, - tooltip: action.getText(context), - ), - child: action.getIcon(), - ); - } - - final blurred = settings.enableBlurEffect; - switch (action) { - case EntryAction.copy: - child = MoveButton( - copy: true, - blurred: blurred, - onChooserValue: (album) => _entryActionDelegate.quickMove(context, album, copy: true), - onPressed: onPressed, - ); - break; - case EntryAction.move: - child = MoveButton( - copy: false, - blurred: blurred, - onChooserValue: (album) => _entryActionDelegate.quickMove(context, album, copy: false), - onPressed: onPressed, - ); - break; - case EntryAction.share: - child = ShareButton( - blurred: blurred, - entries: {mainEntry}, - onChooserValue: (action) => _entryActionDelegate.quickShare(context, action), - onPressed: onPressed, - ); - break; - case EntryAction.toggleFavourite: - child = FavouriteToggler( - entries: {favouriteTargetEntry}, - onPressed: onPressed, - ); - break; - case EntryAction.videoToggleMute: - child = MuteToggler( - controller: videoController, - onPressed: onPressed, - ); - break; - case EntryAction.videoTogglePlay: - child = PlayToggler( - controller: videoController, - onPressed: onPressed, - ); - break; - case EntryAction.videoCaptureFrame: - child = _buildFromListenable(videoController?.canCaptureFrameNotifier); - break; - case EntryAction.videoSelectStreams: - child = _buildFromListenable(videoController?.canSelectStreamNotifier); - break; - case EntryAction.videoSetSpeed: - child = _buildFromListenable(videoController?.canSetSpeedNotifier); - break; - case EntryAction.editRating: - child = RateButton( - blurred: blurred, - onChooserValue: (rating) => _entryActionDelegate.quickRate(context, rating), - onPressed: onPressed, - ); - break; - case EntryAction.editTags: - child = TagButton( - blurred: blurred, - onChooserValue: (filter) => _entryActionDelegate.quickTag(context, filter), - onPressed: onPressed, - ); - break; - default: - child = IconButton( - icon: action.getIcon(), - onPressed: onPressed, - tooltip: action.getText(context), - ); - break; - } return Padding( padding: const EdgeInsets.symmetric(horizontal: padding / 2), child: OverlayButton( scale: scale, - child: child, + child: _buildButtonIcon( + context: context, + action: action, + mainEntry: mainEntry, + pageEntry: pageEntry, + videoController: videoController, + actionDelegate: actionDelegate, + ), ), ); } @@ -390,8 +324,6 @@ class ViewerButtonRowContent extends StatelessWidget { } PopupMenuItem _buildRotateAndFlipMenuItems(BuildContext context) { - final actionDelegate = _entryActionDelegate; - Widget buildDivider() => const SizedBox( height: 16, child: VerticalDivider( @@ -443,8 +375,139 @@ class ViewerButtonRowContent extends StatelessWidget { ), ); } - - EntryActionDelegate get _entryActionDelegate => EntryActionDelegate(mainEntry, pageEntry, collection); - - void _onActionSelected(BuildContext context, EntryAction action) => _entryActionDelegate.onActionSelected(context, action); +} + +Widget _buildButtonIcon({ + required BuildContext context, + required EntryAction action, + required AvesEntry mainEntry, + required AvesEntry pageEntry, + required AvesVideoController? videoController, + required EntryActionDelegate actionDelegate, +}) { + Widget? child; + void onPressed() => actionDelegate.onActionSelected(context, action); + + ValueListenableBuilder _buildFromListenable(ValueListenable? enabledNotifier) { + return ValueListenableBuilder( + valueListenable: enabledNotifier ?? ValueNotifier(false), + builder: (context, canDo, child) => IconButton( + icon: child!, + onPressed: canDo ? onPressed : null, + tooltip: action.getText(context), + ), + child: action.getIcon(), + ); + } + + final blurred = settings.enableBlurEffect; + switch (action) { + case EntryAction.copy: + child = MoveButton( + copy: true, + blurred: blurred, + onChooserValue: (album) => actionDelegate.quickMove(context, album, copy: true), + onPressed: onPressed, + ); + break; + case EntryAction.move: + child = MoveButton( + copy: false, + blurred: blurred, + onChooserValue: (album) => actionDelegate.quickMove(context, album, copy: false), + onPressed: onPressed, + ); + break; + case EntryAction.share: + child = ShareButton( + blurred: blurred, + entries: {mainEntry}, + onChooserValue: (action) => actionDelegate.quickShare(context, action), + onPressed: onPressed, + ); + break; + case EntryAction.toggleFavourite: + final favouriteTargetEntry = mainEntry.isBurst ? pageEntry : mainEntry; + child = FavouriteToggler( + entries: {favouriteTargetEntry}, + onPressed: onPressed, + ); + break; + case EntryAction.videoToggleMute: + child = MuteToggler( + controller: videoController, + onPressed: onPressed, + ); + break; + case EntryAction.videoTogglePlay: + child = PlayToggler( + controller: videoController, + onPressed: onPressed, + ); + break; + case EntryAction.videoCaptureFrame: + child = _buildFromListenable(videoController?.canCaptureFrameNotifier); + break; + case EntryAction.videoSelectStreams: + child = _buildFromListenable(videoController?.canSelectStreamNotifier); + break; + case EntryAction.videoSetSpeed: + child = _buildFromListenable(videoController?.canSetSpeedNotifier); + break; + case EntryAction.editRating: + child = RateButton( + blurred: blurred, + onChooserValue: (rating) => actionDelegate.quickRate(context, rating), + onPressed: onPressed, + ); + break; + case EntryAction.editTags: + child = TagButton( + blurred: blurred, + onChooserValue: (filter) => actionDelegate.quickTag(context, filter), + onPressed: onPressed, + ); + break; + default: + child = IconButton( + icon: action.getIcon(), + onPressed: onPressed, + tooltip: action.getText(context), + ); + break; + } + return child; +} + +Widget _buildButtonCaption({ + required BuildContext context, + required EntryAction action, + required AvesEntry mainEntry, + required AvesEntry pageEntry, + required AvesVideoController? videoController, + required bool enabled, +}) { + switch (action) { + case EntryAction.toggleFavourite: + final favouriteTargetEntry = mainEntry.isBurst ? pageEntry : mainEntry; + return FavouriteTogglerCaption( + entries: {favouriteTargetEntry}, + enabled: enabled, + ); + case EntryAction.videoToggleMute: + return MuteTogglerCaption( + controller: videoController, + enabled: enabled, + ); + case EntryAction.videoTogglePlay: + return PlayTogglerCaption( + controller: videoController, + enabled: enabled, + ); + default: + return CaptionedButtonText( + text: action.getText(context), + enabled: enabled, + ); + } } diff --git a/lib/widgets/viewer/overlay/wallpaper_buttons.dart b/lib/widgets/viewer/overlay/wallpaper_buttons.dart index 92a59d55f..b11b84b77 100644 --- a/lib/widgets/viewer/overlay/wallpaper_buttons.dart +++ b/lib/widgets/viewer/overlay/wallpaper_buttons.dart @@ -8,8 +8,8 @@ import 'package:aves/model/wallpaper_target.dart'; import 'package:aves/services/wallpaper_service.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/dialogs/wallpaper_settings_dialog.dart'; -import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; import 'package:aves/widgets/viewer/visual/conductor.dart'; diff --git a/lib/widgets/viewer/panorama_page.dart b/lib/widgets/viewer/panorama_page.dart index 29f6d2e20..a47584a8b 100644 --- a/lib/widgets/viewer/panorama_page.dart +++ b/lib/widgets/viewer/panorama_page.dart @@ -9,7 +9,7 @@ import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/media_query.dart'; -import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; diff --git a/lib/widgets/welcome_page.dart b/lib/widgets/welcome_page.dart index 599906fd0..c9e4cf468 100644 --- a/lib/widgets/welcome_page.dart +++ b/lib/widgets/welcome_page.dart @@ -10,7 +10,7 @@ import 'package:aves/widgets/common/basic/link_chip.dart'; import 'package:aves/widgets/common/basic/markdown_container.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_logo.dart'; -import 'package:aves/widgets/common/identity/buttons.dart'; +import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/home_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart';