diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d06af5c5..a19bc8ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ All notable changes to this project will be documented in this file. ### Added - Map: create shortcut to custom region and filters -- Video: frame stepping forward/backward actions +- Video: frame stepping forward/backward +- Video: custom playback buttons - English (Shavian) translation (thanks Paranoid Android) ### Changed diff --git a/lib/widgets/settings/video/control_actions.dart b/lib/widgets/settings/video/control_actions.dart index 4a4ccf485..6919bb1fe 100644 --- a/lib/widgets/settings/video/control_actions.dart +++ b/lib/widgets/settings/video/control_actions.dart @@ -2,55 +2,67 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/extensions/build_context.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/viewer/overlay/video/controls.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; -class VideoControlButtonsPage extends StatefulWidget { +class VideoControlButtonsPage extends StatelessWidget { static const routeName = '/settings/video/control_buttons'; - - const VideoControlButtonsPage({super.key}); - - @override - State createState() => _VideoControlButtonsPageState(); -} - -class _VideoControlButtonsPageState extends State { - late final Set _selectedActions; - static const _availableActions = [...EntryActions.videoPlayback, EntryAction.openVideoPlayer]; - @override - void initState() { - super.initState(); - _selectedActions = settings.videoControlActions.toSet(); - } + const VideoControlButtonsPage({super.key}); @override Widget build(BuildContext context) { return AvesScaffold( appBar: AppBar( automaticallyImplyLeading: !settings.useTvLayout, - title: Text(context.l10n.settingsViewerOverlayPageTitle), + title: Text(context.l10n.settingsVideoControlsPageTitle), ), body: SafeArea( - child: PopScope( - canPop: true, - onPopInvokedWithResult: (didPop, result) => settings.videoControlActions = _availableActions.where(_selectedActions.contains).toList(), - child: ListView( - children: _availableActions.map((action) { - return SwitchListTile( - value: _selectedActions.contains(action), - onChanged: (v) => setState(() { - if (v) { - _selectedActions.add(action); - } else { - _selectedActions.remove(action); - } - }), - title: Text(action.getText(context)), - ); - }).toList(), - ), + child: Selector>( + selector: (context, s) => s.videoControlActions, + builder: (context, selectedActionList, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ActionPanel( + child: Container( + alignment: AlignmentDirectional.center, + height: OverlayButton.getSize(context) + 48, + child: selectedActionList.isNotEmpty + ? VideoControlRow(onActionSelected: (_) {}) + : Text( + context.l10n.settingsViewerQuickActionEmpty, + style: Theme.of(context).textTheme.bodySmall, + ), + ), + ), + Expanded( + child: ListView( + children: _availableActions.map((action) { + return SwitchListTile( + value: selectedActionList.contains(action), + onChanged: (v) { + final selectedActionSet = settings.videoControlActions.toSet(); + if (v) { + selectedActionSet.add(action); + } else { + selectedActionSet.remove(action); + } + settings.videoControlActions = _availableActions.where(selectedActionSet.contains).toList(); + }, + title: Text(action.getText(context)), + ); + }).toList(), + ), + ), + ], + ); + }, ), ), ); diff --git a/lib/widgets/viewer/overlay/video/controls.dart b/lib/widgets/viewer/overlay/video/controls.dart index 7cd0624c1..f2929dbd0 100644 --- a/lib/widgets/viewer/overlay/video/controls.dart +++ b/lib/widgets/viewer/overlay/video/controls.dart @@ -1,4 +1,3 @@ -import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/action_controls/togglers/play.dart'; @@ -10,9 +9,9 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class VideoControlRow extends StatelessWidget { - final AvesEntry entry; final AvesVideoController? controller; final Animation scale; + final bool canOpenVideoPlayer; final Function(EntryAction value) onActionSelected; static const double padding = 8; @@ -20,9 +19,9 @@ class VideoControlRow extends StatelessWidget { const VideoControlRow({ super.key, - required this.entry, - required this.controller, - required this.scale, + this.controller, + this.scale = kAlwaysCompleteAnimation, + this.canOpenVideoPlayer = true, required this.onActionSelected, }); @@ -31,29 +30,22 @@ class VideoControlRow extends StatelessWidget { return Selector>( selector: (context, s) => s.videoControlActions, builder: (context, actions, child) { - if (actions.isEmpty) { - return const SizedBox(); - } - - if (actions.length == 1) { - final action = actions.first; - return Padding( - padding: const EdgeInsets.only(left: padding), - child: _buildOverlayButton(context, action, const BorderRadius.all(radius)), - ); - } - return Padding( - padding: const EdgeInsets.only(left: padding), + padding: EdgeInsets.only(left: actions.isEmpty ? 0 : padding), child: Row( mainAxisSize: MainAxisSize.min, textDirection: ViewerBottomOverlay.actionsDirection, children: actions.map((action) { - var borderRadius = BorderRadius.zero; - if (action == actions.first) { - borderRadius = const BorderRadius.horizontal(left: radius); - } else if (action == actions.last) { - borderRadius = const BorderRadius.horizontal(right: radius); + // null radius yields a circular button + BorderRadius? borderRadius; + if (actions.length > 1) { + // zero radius yields a square button + borderRadius = BorderRadius.zero; + if (action == actions.first) { + borderRadius = const BorderRadius.horizontal(left: radius); + } else if (action == actions.last) { + borderRadius = const BorderRadius.horizontal(right: radius); + } } return _buildOverlayButton(context, action, borderRadius); }).toList(), @@ -66,7 +58,7 @@ class VideoControlRow extends StatelessWidget { Widget _buildOverlayButton( BuildContext context, EntryAction action, - BorderRadius borderRadius, + BorderRadius? borderRadius, ) { Widget child; if (action == EntryAction.videoTogglePlay) { @@ -75,7 +67,7 @@ class VideoControlRow extends StatelessWidget { onPressed: () => onActionSelected(action), ); } else { - final enabled = action == EntryAction.openVideoPlayer ? !entry.trashed : true; + final enabled = action == EntryAction.openVideoPlayer ? canOpenVideoPlayer : true; child = IconButton( icon: action.getIcon(), onPressed: enabled ? () => onActionSelected(action) : null, @@ -83,13 +75,15 @@ class VideoControlRow extends StatelessWidget { ); } - child = Padding( - padding: EdgeInsets.only( - left: borderRadius.topLeft.x > 0 ? padding / 3 : 0, - right: borderRadius.topRight.x > 0 ? padding / 3 : 0, - ), - child: child, - ); + if (borderRadius != null) { + child = Padding( + padding: EdgeInsets.only( + left: borderRadius.topLeft.x > 0 ? padding / 3 : 0, + right: borderRadius.topRight.x > 0 ? padding / 3 : 0, + ), + child: child, + ); + } return OverlayButton( scale: scale, diff --git a/lib/widgets/viewer/overlay/video/video.dart b/lib/widgets/viewer/overlay/video/video.dart index 8aaf5cd96..02ad52ae5 100644 --- a/lib/widgets/viewer/overlay/video/video.dart +++ b/lib/widgets/viewer/overlay/video/video.dart @@ -78,9 +78,9 @@ class _VideoControlOverlayState extends State with SingleTi ), ), VideoControlRow( - entry: entry, controller: controller, scale: scale, + canOpenVideoPlayer: !entry.trashed, onActionSelected: widget.onActionSelected, ), ],