#437 tv: default column count, color picker, wheel selector, slideshow captioned buttons
This commit is contained in:
parent
6f17fbcb7e
commit
e8bb1a77f0
7 changed files with 259 additions and 106 deletions
|
@ -235,7 +235,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
final lightTheme = Themes.lightTheme(lightAccent, initialized);
|
final lightTheme = Themes.lightTheme(lightAccent, initialized);
|
||||||
final darkTheme = themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent, initialized) : Themes.darkTheme(darkAccent, initialized);
|
final darkTheme = themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent, initialized) : Themes.darkTheme(darkAccent, initialized);
|
||||||
return Shortcuts(
|
return Shortcuts(
|
||||||
shortcuts: <LogicalKeySet, Intent>{
|
shortcuts: {
|
||||||
// handle Android TV remote `select` button
|
// handle Android TV remote `select` button
|
||||||
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
|
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,12 +52,13 @@ import 'package:tuple/tuple.dart';
|
||||||
class CollectionGrid extends StatefulWidget {
|
class CollectionGrid extends StatefulWidget {
|
||||||
final String settingsRouteKey;
|
final String settingsRouteKey;
|
||||||
|
|
||||||
static const int columnCountDefault = 4;
|
|
||||||
static const double extentMin = 46;
|
static const double extentMin = 46;
|
||||||
static const double extentMax = 300;
|
static const double extentMax = 300;
|
||||||
static const double fixedExtentLayoutSpacing = 2;
|
static const double fixedExtentLayoutSpacing = 2;
|
||||||
static const double mosaicLayoutSpacing = 4;
|
static const double mosaicLayoutSpacing = 4;
|
||||||
|
|
||||||
|
static int get columnCountDefault => device.isTelevision ? 6 : 4;
|
||||||
|
|
||||||
const CollectionGrid({
|
const CollectionGrid({
|
||||||
super.key,
|
super.key,
|
||||||
required this.settingsRouteKey,
|
required this.settingsRouteKey,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/fx/borders.dart';
|
import 'package:aves/widgets/common/fx/borders.dart';
|
||||||
|
@ -36,7 +37,6 @@ class ColorListTile extends StatelessWidget {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final color = await showDialog<Color>(
|
final color = await showDialog<Color>(
|
||||||
context: context,
|
context: context,
|
||||||
// TODO TLAD [tv] color pick
|
|
||||||
builder: (context) => ColorPickerDialog(
|
builder: (context) => ColorPickerDialog(
|
||||||
initialValue: value,
|
initialValue: value,
|
||||||
),
|
),
|
||||||
|
@ -72,18 +72,25 @@ class _ColorPickerDialogState extends State<ColorPickerDialog> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isTelevision = device.isTelevision;
|
||||||
return AvesDialog(
|
return AvesDialog(
|
||||||
scrollableContent: [
|
scrollableContent: [
|
||||||
ColorPicker(
|
ColorPicker(
|
||||||
color: color,
|
color: color,
|
||||||
onColorChanged: (v) => color = v,
|
onColorChanged: (v) => color = v,
|
||||||
pickersEnabled: const {
|
pickersEnabled: isTelevision
|
||||||
ColorPickerType.primary: false,
|
? const {
|
||||||
ColorPickerType.accent: false,
|
ColorPickerType.primary: true,
|
||||||
ColorPickerType.wheel: true,
|
ColorPickerType.accent: false,
|
||||||
},
|
}
|
||||||
|
: const {
|
||||||
|
ColorPickerType.primary: false,
|
||||||
|
ColorPickerType.accent: false,
|
||||||
|
ColorPickerType.wheel: true,
|
||||||
|
},
|
||||||
hasBorder: true,
|
hasBorder: true,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
|
subheading: isTelevision ? const SizedBox(height: 16) : null,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class WheelSelector<T> extends StatefulWidget {
|
class WheelSelector<T> extends StatefulWidget {
|
||||||
final ValueNotifier<T> valueNotifier;
|
final ValueNotifier<T> valueNotifier;
|
||||||
|
@ -19,7 +22,8 @@ class WheelSelector<T> extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _WheelSelectorState<T> extends State<WheelSelector<T>> {
|
class _WheelSelectorState<T> extends State<WheelSelector<T>> {
|
||||||
late final ScrollController _controller;
|
late final FixedExtentScrollController _controller;
|
||||||
|
final ValueNotifier<bool> _focusedNotifier = ValueNotifier(false);
|
||||||
|
|
||||||
static const itemSize = Size(40, 40);
|
static const itemSize = Size(40, 40);
|
||||||
|
|
||||||
|
@ -30,66 +34,139 @@ class _WheelSelectorState<T> extends State<WheelSelector<T>> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
var indexOf = values.indexOf(valueNotifier.value);
|
|
||||||
_controller = FixedExtentScrollController(
|
_controller = FixedExtentScrollController(
|
||||||
initialItem: indexOf,
|
initialItem: values.indexOf(valueNotifier.value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
_focusedNotifier.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const background = Colors.transparent;
|
const background = Colors.transparent;
|
||||||
final foreground = DefaultTextStyle.of(context).style.color!;
|
final foreground = DefaultTextStyle.of(context).style.color!;
|
||||||
|
final transitionDuration = context.select<DurationsData, Duration>((v) => v.formTransition);
|
||||||
|
|
||||||
// TODO TLAD [tv] wheel traversal
|
return FocusableActionDetector(
|
||||||
return NotificationListener<ScrollNotification>(
|
shortcuts: const {
|
||||||
// cancel notification bubbling so that the dialog scroll bar
|
SingleActivator(LogicalKeyboardKey.arrowUp): _AdjustValueIntent.up(),
|
||||||
// does not misinterpret wheel scrolling for dialog content scrolling
|
SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustValueIntent.down(),
|
||||||
onNotification: (notification) => true,
|
},
|
||||||
child: Padding(
|
actions: {
|
||||||
padding: const EdgeInsets.all(8),
|
_AdjustValueIntent: CallbackAction<_AdjustValueIntent>(onInvoke: _onAdjustValueIntent),
|
||||||
child: SizedBox(
|
},
|
||||||
width: itemSize.width,
|
onShowFocusHighlight: (v) => _focusedNotifier.value = v,
|
||||||
height: itemSize.height * 3,
|
child: NotificationListener<ScrollNotification>(
|
||||||
child: ShaderMask(
|
// cancel notification bubbling so that the dialog scroll bar
|
||||||
shaderCallback: LinearGradient(
|
// does not misinterpret wheel scrolling for dialog content scrolling
|
||||||
begin: Alignment.topCenter,
|
onNotification: (notification) => true,
|
||||||
end: Alignment.bottomCenter,
|
child: Padding(
|
||||||
colors: [
|
padding: const EdgeInsets.all(8),
|
||||||
background,
|
child: Stack(
|
||||||
foreground,
|
children: [
|
||||||
foreground,
|
Positioned.fill(
|
||||||
background,
|
child: Center(
|
||||||
],
|
child: ValueListenableBuilder<bool>(
|
||||||
).createShader,
|
valueListenable: _focusedNotifier,
|
||||||
child: Theme(
|
builder: (context, focused, child) {
|
||||||
data: Theme.of(context).copyWith(
|
return AnimatedContainer(
|
||||||
scrollbarTheme: ScrollbarThemeData(
|
width: itemSize.width,
|
||||||
thumbVisibility: MaterialStateProperty.all(false),
|
height: itemSize.height,
|
||||||
|
duration: transitionDuration,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foreground.withOpacity(focused ? .2 : 0),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: ListWheelScrollView(
|
SizedBox(
|
||||||
controller: _controller,
|
width: itemSize.width,
|
||||||
physics: const FixedExtentScrollPhysics(parent: BouncingScrollPhysics()),
|
height: itemSize.height * 3,
|
||||||
diameterRatio: 1.2,
|
child: ShaderMask(
|
||||||
itemExtent: itemSize.height,
|
shaderCallback: LinearGradient(
|
||||||
squeeze: 1.3,
|
begin: Alignment.topCenter,
|
||||||
onSelectedItemChanged: (i) => valueNotifier.value = values[i],
|
end: Alignment.bottomCenter,
|
||||||
children: values
|
colors: [
|
||||||
.map((i) => SizedBox.fromSize(
|
background,
|
||||||
size: itemSize,
|
foreground,
|
||||||
child: Text(
|
foreground,
|
||||||
'$i',
|
background,
|
||||||
textAlign: widget.textAlign,
|
],
|
||||||
style: widget.textStyle,
|
).createShader,
|
||||||
),
|
child: Theme(
|
||||||
))
|
data: Theme.of(context).copyWith(
|
||||||
.toList(),
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
|
thumbVisibility: MaterialStateProperty.all(false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ListWheelScrollView(
|
||||||
|
controller: _controller,
|
||||||
|
physics: const FixedExtentScrollPhysics(parent: BouncingScrollPhysics()),
|
||||||
|
diameterRatio: 1.2,
|
||||||
|
itemExtent: itemSize.height,
|
||||||
|
squeeze: 1.3,
|
||||||
|
onSelectedItemChanged: (i) => valueNotifier.value = values[i],
|
||||||
|
children: values
|
||||||
|
.map((i) => SizedBox.fromSize(
|
||||||
|
size: itemSize,
|
||||||
|
child: Text(
|
||||||
|
'$i',
|
||||||
|
textAlign: widget.textAlign,
|
||||||
|
style: widget.textStyle,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onAdjustValueIntent(_AdjustValueIntent intent) {
|
||||||
|
late int delta;
|
||||||
|
switch (intent.type) {
|
||||||
|
case _ValueAdjustmentType.up:
|
||||||
|
delta = -1;
|
||||||
|
break;
|
||||||
|
case _ValueAdjustmentType.down:
|
||||||
|
delta = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final targetItem = _controller.selectedItem + delta;
|
||||||
|
final duration = context.read<DurationsData>().formTransition;
|
||||||
|
if (duration > Duration.zero) {
|
||||||
|
_controller.animateToItem(targetItem, duration: duration, curve: Curves.easeInOutCubic);
|
||||||
|
} else {
|
||||||
|
_controller.jumpToItem(targetItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AdjustValueIntent extends Intent {
|
||||||
|
const _AdjustValueIntent({
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _AdjustValueIntent.up() : type = _ValueAdjustmentType.up;
|
||||||
|
|
||||||
|
const _AdjustValueIntent.down() : type = _ValueAdjustmentType.down;
|
||||||
|
|
||||||
|
final _ValueAdjustmentType type;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _ValueAdjustmentType {
|
||||||
|
up,
|
||||||
|
down,
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,7 +211,7 @@ class _FilterGridState<T extends CollectionFilter> extends State<FilterGrid<T>>
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_tileExtentController ??= TileExtentController(
|
_tileExtentController ??= TileExtentController(
|
||||||
settingsRouteKey: widget.settingsRouteKey ?? context.currentRouteName!,
|
settingsRouteKey: widget.settingsRouteKey ?? context.currentRouteName!,
|
||||||
columnCountDefault: 3,
|
columnCountDefault: device.isTelevision ? 4 : 3,
|
||||||
extentMin: 60,
|
extentMin: 60,
|
||||||
extentMax: 300,
|
extentMax: 300,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
|
|
|
@ -283,7 +283,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
onImagePageRequested: () => _goToVerticalPage(imagePage),
|
onImagePageRequested: () => _goToVerticalPage(imagePage),
|
||||||
onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry),
|
onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry),
|
||||||
),
|
),
|
||||||
..._buildOverlays(),
|
..._buildOverlays().map(_decorateOverlay),
|
||||||
const TopGestureAreaProtector(),
|
const TopGestureAreaProtector(),
|
||||||
const SideGestureAreaProtector(),
|
const SideGestureAreaProtector(),
|
||||||
const BottomGestureAreaProtector(),
|
const BottomGestureAreaProtector(),
|
||||||
|
@ -294,6 +294,19 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _decorateOverlay(Widget overlay) {
|
||||||
|
return ValueListenableBuilder<double>(
|
||||||
|
valueListenable: _overlayAnimationController,
|
||||||
|
builder: (context, animation, child) {
|
||||||
|
return Visibility(
|
||||||
|
visible: !_overlayAnimationController.isDismissed,
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: overlay,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> _buildOverlays() {
|
List<Widget> _buildOverlays() {
|
||||||
final appMode = context.read<ValueNotifier<AppMode>>().value;
|
final appMode = context.read<ValueNotifier<AppMode>>().value;
|
||||||
switch (appMode) {
|
switch (appMode) {
|
||||||
|
@ -324,7 +337,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
preferBelow: false,
|
preferBelow: false,
|
||||||
),
|
),
|
||||||
child: SlideshowButtons(
|
child: SlideshowButtons(
|
||||||
scale: _overlayButtonScale,
|
animationController: _overlayAnimationController,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -365,17 +378,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
child = ValueListenableBuilder<double>(
|
|
||||||
valueListenable: _overlayAnimationController,
|
|
||||||
builder: (context, animation, child) {
|
|
||||||
return Visibility(
|
|
||||||
visible: !_overlayAnimationController.isDismissed,
|
|
||||||
child: child!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,16 +476,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
return ValueListenableBuilder<double>(
|
return child;
|
||||||
valueListenable: _overlayAnimationController,
|
|
||||||
builder: (context, animation, child) {
|
|
||||||
return Visibility(
|
|
||||||
visible: !_overlayAnimationController.isDismissed,
|
|
||||||
child: child!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onVideoAction(BuildContext context, AvesVideoController controller, EntryAction action) async {
|
Future<void> _onVideoAction(BuildContext context, AvesVideoController controller, EntryAction action) async {
|
||||||
|
|
|
@ -1,44 +1,119 @@
|
||||||
import 'package:aves/model/actions/slideshow_actions.dart';
|
import 'package:aves/model/actions/slideshow_actions.dart';
|
||||||
|
import 'package:aves/model/device.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/common/identity/buttons/overlay_button.dart';
|
||||||
|
import 'package:aves/widgets/viewer/entry_vertical_pager.dart';
|
||||||
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart';
|
import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart';
|
||||||
import 'package:aves/widgets/viewer/slideshow_page.dart';
|
import 'package:aves/widgets/viewer/slideshow_page.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class SlideshowButtons extends StatelessWidget {
|
class SlideshowButtons extends StatefulWidget {
|
||||||
final Animation<double> scale;
|
final AnimationController animationController;
|
||||||
|
|
||||||
const SlideshowButtons({
|
const SlideshowButtons({
|
||||||
super.key,
|
super.key,
|
||||||
required this.scale,
|
required this.animationController,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SlideshowButtons> createState() => _SlideshowButtonsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SlideshowButtonsState extends State<SlideshowButtons> {
|
||||||
|
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
|
||||||
|
late Animation<double> _buttonScale;
|
||||||
|
|
||||||
|
static const List<SlideshowAction> _actions = [
|
||||||
|
SlideshowAction.resume,
|
||||||
|
SlideshowAction.showInCollection,
|
||||||
|
];
|
||||||
|
static const double _padding = ViewerButtonRowContent.padding;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_registerWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant SlideshowButtons oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
_unregisterWidget(oldWidget);
|
||||||
|
_registerWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_unregisterWidget(widget);
|
||||||
|
_buttonRowFocusScopeNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerWidget(SlideshowButtons widget) {
|
||||||
|
final animationController = widget.animationController;
|
||||||
|
_buttonScale = CurvedAnimation(
|
||||||
|
parent: animationController,
|
||||||
|
// a little bounce at the top
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
);
|
||||||
|
animationController.addStatusListener(_onAnimationStatusChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _unregisterWidget(SlideshowButtons widget) {
|
||||||
|
widget.animationController.removeStatusListener(_onAnimationStatusChanged);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// TODO TLAD [tv] captioned buttons
|
return FocusableActionDetector(
|
||||||
const padding = ViewerButtonRowContent.padding;
|
focusNode: _buttonRowFocusScopeNode,
|
||||||
return SafeArea(
|
shortcuts: device.isTelevision ? const {SingleActivator(LogicalKeyboardKey.arrowUp): TvShowLessInfoIntent()} : null,
|
||||||
child: Padding(
|
actions: {TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context))},
|
||||||
padding: const EdgeInsets.only(left: padding / 2, right: padding / 2, bottom: padding),
|
child: device.isTelevision
|
||||||
child: Row(
|
? Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
SlideshowAction.resume,
|
children: _actions.map((action) {
|
||||||
SlideshowAction.showInCollection,
|
return CaptionedButton(
|
||||||
]
|
scale: _buttonScale,
|
||||||
.map((action) => Padding(
|
icon: action.getIcon(),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: padding / 2),
|
caption: action.getText(context),
|
||||||
child: OverlayButton(
|
onPressed: () => _onAction(context, action),
|
||||||
scale: scale,
|
);
|
||||||
child: IconButton(
|
}).toList(),
|
||||||
icon: action.getIcon(),
|
)
|
||||||
onPressed: () => SlideshowActionNotification(action).dispatch(context),
|
: SafeArea(
|
||||||
tooltip: action.getText(context),
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.only(left: _padding / 2, right: _padding / 2, bottom: _padding),
|
||||||
),
|
child: Row(
|
||||||
))
|
mainAxisSize: MainAxisSize.min,
|
||||||
.toList(),
|
children: _actions
|
||||||
),
|
.map((action) => Padding(
|
||||||
),
|
padding: const EdgeInsets.symmetric(horizontal: _padding / 2),
|
||||||
|
child: OverlayButton(
|
||||||
|
scale: _buttonScale,
|
||||||
|
child: IconButton(
|
||||||
|
icon: action.getIcon(),
|
||||||
|
onPressed: () => _onAction(context, action),
|
||||||
|
tooltip: action.getText(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onAction(BuildContext context, SlideshowAction action) => SlideshowActionNotification(action).dispatch(context);
|
||||||
|
|
||||||
|
void _onAnimationStatusChanged(AnimationStatus status) {
|
||||||
|
if (status == AnimationStatus.completed) {
|
||||||
|
_buttonRowFocusScopeNode.children.firstOrNull?.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue