#437 tv: toggle button state
This commit is contained in:
parent
1cba919385
commit
da751190c5
47 changed files with 570 additions and 289 deletions
|
@ -114,6 +114,12 @@ class EntryActions {
|
|||
EntryAction.videoSettings,
|
||||
];
|
||||
|
||||
static const videoPlayback = [
|
||||
EntryAction.videoReplay10,
|
||||
EntryAction.videoTogglePlay,
|
||||
EntryAction.videoSkip10,
|
||||
];
|
||||
|
||||
static const commonMetadataActions = [
|
||||
EntryAction.editDate,
|
||||
EntryAction.editLocation,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
|
@ -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';
|
||||
|
|
@ -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';
|
|
@ -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';
|
|
@ -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> {
|
|
@ -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 {
|
|
@ -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> {
|
|
@ -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';
|
||||
|
|
@ -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';
|
|
@ -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';
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
105
lib/widgets/common/identity/buttons/captioned_button.dart
Normal file
105
lib/widgets/common/identity/buttons/captioned_button.dart
Normal 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!;
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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: () {},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in a new issue