#437 tv: toggle button state

This commit is contained in:
Thibault Deckers 2022-12-15 18:59:20 +01:00
parent 1cba919385
commit da751190c5
47 changed files with 570 additions and 289 deletions

View file

@ -114,6 +114,12 @@ class EntryActions {
EntryAction.videoSettings,
];
static const videoPlayback = [
EntryAction.videoReplay10,
EntryAction.videoTogglePlay,
EntryAction.videoSkip10,
];
static const commonMetadataActions = [
EntryAction.editDate,
EntryAction.editLocation,

View file

@ -27,4 +27,4 @@ extension ExtraMapClusterAction on MapClusterAction {
return AIcons.clear;
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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';

View file

@ -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';

View file

@ -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<CollectionAppBar> 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<CollectionAppBar> 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<CollectionAppBar> 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<CollectionAppBar> 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<CollectionAppBar> 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<AvesEntry> selection}) {
Widget _buildButtonIcon(
BuildContext context,
EntrySetAction action, {
required bool enabled,
required Selection<AvesEntry> selection,
}) {
final onPressed = enabled ? () => _onActionSelected(action) : null;
switch (action) {
case EntrySetAction.toggleTitleSearch:
@ -455,6 +458,24 @@ class _CollectionAppBarState extends State<CollectionAppBar> 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<EntrySetAction> _toMenuItem(EntrySetAction action, {required bool enabled, required Selection<AvesEntry> selection}) {
late Widget child;
switch (action) {

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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<int> {

View file

@ -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 {

View file

@ -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<ShareAction> {

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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<FavouriteToggler> {
@override
void dispose() {
favourites.removeListener(_onChanged);
isFavouriteNotifier.dispose();
super.dispose();
}
@ -94,3 +96,60 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite);
}
}
class FavouriteTogglerCaption extends StatefulWidget {
final Set<AvesEntry> entries;
final bool enabled;
const FavouriteTogglerCaption({
super.key,
required this.entries,
required this.enabled,
});
@override
State<FavouriteTogglerCaption> createState() => _FavouriteTogglerCaptionState();
}
class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
final ValueNotifier<bool> isFavouriteNotifier = ValueNotifier(false);
Set<AvesEntry> 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<bool>(
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);
}
}

View file

@ -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<bool>(
valueListenable: controller?.canMuteNotifier ?? ValueNotifier(false),
builder: (context, canDo, child) {
return StreamBuilder<double>(
stream: controller?.volumeStream ?? Stream.value(1.0),
builder: (context, snapshot) {
return CaptionedButtonText(
text: isMuted ? context.l10n.videoActionUnmute : context.l10n.videoActionMute,
enabled: canDo && enabled,
);
},
);
},
);
}
}

View file

@ -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<PlayToggler> 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<VideoStatus>(
stream: controller?.statusStream ?? Stream.value(VideoStatus.idle),
builder: (context, snapshot) {
return CaptionedButtonText(
text: isPlaying ? context.l10n.videoActionPause : context.l10n.videoActionPlay,
enabled: enabled,
);
},
);
}
}

View file

@ -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<Query?, bool>(
selector: (context, query) => query?.enabled ?? false,
builder: (context, queryEnabled, child) {
return CaptionedButtonText(
text: queryEnabled ? context.l10n.collectionActionHideTitleSearch : context.l10n.collectionActionShowTitleSearch,
enabled: enabled,
);
},
);
}
}

View file

@ -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<double> 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!;
}

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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<T extends CollectionFilter, CSAD extends ChipSetAct
children: [
if (isTelevision)
SizedBox(
height: ActionButton.getTelevisionButtonHeight(context),
height: CaptionedButton.getTelevisionButtonHeight(context),
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 8),
scrollDirection: Axis.horizontal,
@ -166,7 +166,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
double get appBarContentHeight {
double height = kToolbarHeight;
if (device.isTelevision) {
height += ActionButton.getTelevisionButtonHeight(context);
height += CaptionedButton.getTelevisionButtonHeight(context);
}
if (context.read<Query>().enabled) {
height += FilterQueryBar.preferredHeight;
@ -281,12 +281,10 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
...ChipSetActions.general,
...isSelecting ? ChipSetActions.selection : ChipSetActions.browsing,
].where(isVisible).map((action) {
// TODO TLAD [tv] togglers cf `FilterGridAppBar.toMenuItem`
final enabled = canApply(action);
return ActionButton(
text: action.getText(context),
icon: action.getIcon(),
enabled: enabled,
return CaptionedButton(
iconButton: _buildButtonIcon(context, actionDelegate, action, enabled: enabled),
captionText: _buildButtonCaption(context, action, enabled: enabled),
onPressed: enabled ? () => _onActionSelected(context, action, actionDelegate) : null,
);
}).toList();
@ -302,7 +300,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
final isSelecting = selection.isSelecting;
final quickActionButtons = (isSelecting ? selectionQuickActions : browsingQuickActions).where(isVisible).map(
(action) => _toActionButton(context, actionDelegate, action, enabled: canApply(action)),
(action) => _buildButtonIcon(context, actionDelegate, action, enabled: canApply(action)),
);
return [
@ -342,7 +340,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
];
}
Widget _toActionButton(
Widget _buildButtonIcon(
BuildContext context,
CSAD actionDelegate,
ChipSetAction action, {
@ -370,6 +368,24 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
}
}
Widget _buildButtonCaption(
BuildContext context,
ChipSetAction action, {
required bool enabled,
}) {
switch (action) {
case ChipSetAction.toggleTitleSearch:
return TitleSearchTogglerCaption(
enabled: enabled,
);
default:
return CaptionedButtonText(
text: action.getText(context),
enabled: enabled,
);
}
}
void _onActivityChanged() {
if (context.read<Selection<FilterGridItem<T>>>().isSelecting) {
_browseToSelectAnimation.forward();

View file

@ -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;
}
}

View file

@ -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<T extends Object> extends StatelessWidget {
final ValueNotifier<T?> draggedQuickAction;
final ValueNotifier<T?> 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<T extends Object> 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<T extends Object> extends StatelessWidget {
void _setPanelHighlight(bool flag) => panelHighlight.value = flag;
static double heightFor(BuildContext context, List<String> 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;

View file

@ -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<T extends Object> extends StatelessWidget {
final String title, bannerText;
final List<List<T>> allAvailableActions;
final Widget? Function(T action) actionIcon;
final Widget Function(T action) actionIcon;
final String Function(BuildContext context, T action) actionText;
final List<T> Function() load;
final void Function(List<T> actions) save;
@ -58,7 +58,7 @@ class QuickActionEditorPage<T extends Object> extends StatelessWidget {
class QuickActionEditorBody<T extends Object> extends StatefulWidget {
final String bannerText;
final List<List<T>> allAvailableActions;
final Widget? Function(T action) actionIcon;
final Widget Function(T action) actionIcon;
final String Function(BuildContext context, T action) actionText;
final List<T> Function() load;
final void Function(List<T> actions) save;
@ -208,10 +208,11 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
insertAction: _insertQuickAction,
removeAction: _removeQuickAction,
onTargetLeave: _onQuickActionTargetLeave,
draggableFeedbackBuilder: (action) => 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<T extends Object> extends State<QuickActionEdi
padding: const EdgeInsets.symmetric(vertical: _QuickActionEditorBodyState.quickActionVerticalPadding, horizontal: 4),
child: OverlayButton(
child: IconButton(
icon: widget.actionIcon(action) ?? const SizedBox(),
icon: widget.actionIcon(action),
onPressed: () {},
),
),

View file

@ -1,5 +1,5 @@
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/viewer/overlay/common.dart';
import 'package:flutter/widgets.dart';
enum QuickActionPlacement { header, action, footer }

View file

@ -10,7 +10,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/identity/buttons.dart';
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
import 'package:aves/widgets/home_widget.dart';
import 'package:aves/widgets/settings/common/collection_tile.dart';
import 'package:aves/widgets/settings/common/tiles.dart';

View file

@ -2,7 +2,7 @@ import 'package:aves/model/filters/album.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/icons.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/filter_grids/album_pick.dart';
import 'package:aves/widgets/navigation/drawer/tile.dart';
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';

View file

@ -8,7 +8,7 @@ import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/constants.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.dart';
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
import 'package:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/settings/privacy/file_picker/crumb_line.dart';
import 'package:collection/collection.dart';

View file

@ -5,7 +5,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.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:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/settings/privacy/file_picker/file_picker_page.dart';
import 'package:flutter/material.dart';

View file

@ -16,8 +16,8 @@ import 'package:aves/theme/colors.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/file_utils.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/rate_button.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/tag_button.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/rate_button.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/tag_button.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:aves/model/entry.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/panorama_page.dart';
import 'package:flutter/material.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/model/actions/slideshow_actions.dart';
import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart';
import 'package:aves/widgets/viewer/slideshow_page.dart';
import 'package:flutter/material.dart';
@ -14,7 +14,7 @@ class SlideshowButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO TLAD [tv] action buttons
// TODO TLAD [tv] captioned buttons
const padding = ViewerButtonRowContent.padding;
return SafeArea(
child: Padding(

View file

@ -1,8 +1,8 @@
import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:aves/widgets/viewer/overlay/video/play_toggler.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/common/action_controls/togglers/play.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

View file

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/overlay/video/controls.dart';
import 'package:aves/widgets/viewer/overlay/video/progress_bar.dart';
import 'package:aves/widgets/viewer/video/controller.dart';

View file

@ -5,20 +5,20 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/app_bar/favourite_toggler.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/move_button.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/rate_button.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/share_button.dart';
import 'package:aves/widgets/common/app_bar/quick_choosers/tag_button.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/move_button.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/rate_button.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/share_button.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/tag_button.dart';
import 'package:aves/widgets/common/action_controls/togglers/favourite.dart';
import 'package:aves/widgets/common/action_controls/togglers/mute.dart';
import 'package:aves/widgets/common/action_controls/togglers/play.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup_menu_button.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/viewer/action/entry_action_delegate.dart';
import 'package:aves/widgets/viewer/notifications.dart';
import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:aves/widgets/viewer/overlay/video/mute_toggler.dart';
import 'package:aves/widgets/viewer/overlay/video/play_toggler.dart';
import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:collection/collection.dart';
@ -49,16 +49,17 @@ class ViewerButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection);
if (device.isTelevision) {
return _TvButtonRowContent(
actionDelegate: actionDelegate,
scale: scale,
mainEntry: mainEntry,
pageEntry: pageEntry,
collection: collection,
);
}
final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection);
final trashed = mainEntry.trashed;
return SafeArea(
top: false,
@ -75,6 +76,7 @@ class ViewerButtons extends StatelessWidget {
final exportActions = EntryActions.export.where((action) => !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<double> 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<VideoConductor, AvesVideoController?>(
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<EntryAction> quickActions, topLevelActions, exportActions, videoActions;
final Animation<double> scale;
final AvesEntry mainEntry, pageEntry;
final CollectionLens? collection;
final ValueNotifier<String?> _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<bool> _buildFromListenable(ValueListenable<bool>? enabledNotifier) {
return ValueListenableBuilder<bool>(
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<EntryAction> _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<bool> _buildFromListenable(ValueListenable<bool>? enabledNotifier) {
return ValueListenableBuilder<bool>(
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,
);
}
}

View file

@ -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';

View file

@ -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';

View file

@ -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';