#1177 slideshow: fixed bottom overlay layout on inset transition

This commit is contained in:
Thibault Deckers 2024-09-13 21:30:35 +02:00
parent 013b2631aa
commit 535d4c0d00
3 changed files with 144 additions and 72 deletions

View file

@ -412,17 +412,17 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} }
Widget _buildSlideshowBottomOverlay(Size availableSize) { Widget _buildSlideshowBottomOverlay(Size availableSize) {
return SizedBox.fromSize( return TooltipTheme(
size: availableSize, data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: Align( child: Align(
alignment: AlignmentDirectional.bottomEnd, alignment: AlignmentDirectional.bottomEnd,
child: TooltipTheme( child: SlideshowBottomOverlay(
data: TooltipTheme.of(context).copyWith( animationController: _overlayAnimationController,
preferBelow: false, availableSize: availableSize,
), viewInsets: _frozenViewInsets,
child: SlideshowButtons( viewPadding: _frozenViewPadding,
animationController: _overlayAnimationController,
),
), ),
), ),
); );

View file

@ -21,7 +21,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class ViewerBottomOverlay extends StatefulWidget { class ViewerBottomOverlay extends StatelessWidget {
final List<AvesEntry> entries; final List<AvesEntry> entries;
final int index; final int index;
final CollectionLens? collection; final CollectionLens? collection;
@ -33,6 +33,10 @@ class ViewerBottomOverlay extends StatefulWidget {
// always keep action buttons in the lower right corner, even with RTL locales // always keep action buttons in the lower right corner, even with RTL locales
static const actionsDirection = TextDirection.ltr; static const actionsDirection = TextDirection.ltr;
AvesEntry? get entry {
return index < entries.length ? entries[index] : null;
}
const ViewerBottomOverlay({ const ViewerBottomOverlay({
super.key, super.key,
required this.entries, required this.entries,
@ -45,27 +49,6 @@ class ViewerBottomOverlay extends StatefulWidget {
required this.multiPageController, required this.multiPageController,
}); });
@override
State<StatefulWidget> createState() => _ViewerBottomOverlayState();
static double actionSafeHeight(BuildContext context) {
final mqPaddingBottom = context.select<MediaQueryData, double>((mq) => max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom));
final buttonHeight = ViewerButtons.preferredHeight(context);
final thumbnailHeight = (settings.showOverlayThumbnailPreview ? ViewerThumbnailPreview.preferredHeight : 0);
return mqPaddingBottom + buttonHeight + thumbnailHeight;
}
}
class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
List<AvesEntry> get entries => widget.entries;
AvesEntry? get entry {
final index = widget.index;
return index < entries.length ? entries[index] : null;
}
MultiPageController? get multiPageController => widget.multiPageController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final mainEntry = entry; final mainEntry = entry;
@ -73,15 +56,15 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
Widget _buildContent({AvesEntry? pageEntry}) => _BottomOverlayContent( Widget _buildContent({AvesEntry? pageEntry}) => _BottomOverlayContent(
entries: entries, entries: entries,
index: widget.index, index: index,
mainEntry: mainEntry, mainEntry: mainEntry,
pageEntry: pageEntry ?? mainEntry, pageEntry: pageEntry ?? mainEntry,
collection: widget.collection, collection: collection,
availableSize: widget.availableSize, availableSize: availableSize,
viewInsets: widget.viewInsets, viewInsets: viewInsets,
viewPadding: widget.viewPadding, viewPadding: viewPadding,
multiPageController: multiPageController, multiPageController: multiPageController,
animationController: widget.animationController, animationController: animationController,
); );
Widget child = multiPageController != null Widget child = multiPageController != null
@ -102,6 +85,13 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
child: child, child: child,
); );
} }
static double actionSafeHeight(BuildContext context) {
final mqPaddingBottom = context.select<MediaQueryData, double>((mq) => max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom));
final buttonHeight = ViewerButtons.preferredHeight(context);
final thumbnailHeight = (settings.showOverlayThumbnailPreview ? ViewerThumbnailPreview.preferredHeight : 0);
return mqPaddingBottom + buttonHeight + thumbnailHeight;
}
} }
class _BottomOverlayContent extends StatefulWidget { class _BottomOverlayContent extends StatefulWidget {

View file

@ -1,21 +1,64 @@
import 'dart:math';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/view/view.dart'; import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/extensions/media_query.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_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/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/controls/intents.dart'; import 'package:aves/widgets/viewer/controls/intents.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves/widgets/viewer/overlay/bottom.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:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
class SlideshowBottomOverlay extends StatelessWidget {
final AnimationController animationController;
final Size availableSize;
final EdgeInsets? viewInsets, viewPadding;
const SlideshowBottomOverlay({
super.key,
required this.animationController,
required this.availableSize,
this.viewInsets,
this.viewPadding,
});
@override
Widget build(BuildContext context) {
return Selector<MediaQueryData, double>(
selector: (context, mq) => max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom),
builder: (context, mqPaddingBottom, child) {
return Padding(
padding: EdgeInsets.only(bottom: mqPaddingBottom),
child: child,
);
},
child: SlideshowButtons(
availableSize: availableSize,
viewInsets: viewInsets,
viewPadding: viewPadding,
animationController: animationController,
),
);
}
}
class SlideshowButtons extends StatefulWidget { class SlideshowButtons extends StatefulWidget {
final Size availableSize;
final EdgeInsets? viewInsets, viewPadding;
final AnimationController animationController; final AnimationController animationController;
const SlideshowButtons({ const SlideshowButtons({
super.key, super.key,
required this.availableSize,
required this.viewInsets,
required this.viewPadding,
required this.animationController, required this.animationController,
}); });
@ -70,7 +113,8 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FocusableActionDetector( final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero);
final viewerButtonRow = FocusableActionDetector(
focusNode: _buttonRowFocusScopeNode, focusNode: _buttonRowFocusScopeNode,
shortcuts: settings.useTvLayout shortcuts: settings.useTvLayout
? const { ? const {
@ -80,40 +124,78 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
actions: { actions: {
TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context)), TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context)),
}, },
child: settings.useTvLayout child: SafeArea(
? Row( top: false,
mainAxisAlignment: MainAxisAlignment.center, bottom: false,
crossAxisAlignment: CrossAxisAlignment.start, minimum: EdgeInsets.only(
children: _actions.map((action) { left: viewInsetsPadding.left,
return CaptionedButton( right: viewInsetsPadding.right,
scale: _buttonScale, ),
icon: action.getIcon(), child: _buildButtons(context),
caption: action.getText(context), ),
onPressed: () => _onAction(context, action), );
);
}).toList(), final availableWidth = widget.availableSize.width;
) return SizedBox(
: SafeArea( width: availableWidth,
child: Padding( child: Column(
padding: const EdgeInsets.only(left: _padding / 2, right: _padding / 2, bottom: _padding), mainAxisSize: MainAxisSize.min,
child: Row( crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, children: [
children: _actions viewerButtonRow,
.map((action) => Padding( ],
padding: const EdgeInsets.symmetric(horizontal: _padding / 2), ),
child: OverlayButton( );
scale: _buttonScale, }
child: IconButton(
icon: action.getIcon(), Widget _buildButtons(BuildContext context) {
onPressed: () => _onAction(context, action), if (settings.useTvLayout) {
tooltip: action.getText(context), return _buildTvButtonRowContent(context);
), }
),
)) return SafeArea(
.toList(), top: false,
), bottom: false,
), child: Padding(
), padding: const EdgeInsets.only(left: _padding / 2, right: _padding / 2, bottom: _padding),
child: Row(
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
const Spacer(),
..._actions.map((action) => _buildOverlayButton(context, action)),
],
),
),
);
}
Widget _buildOverlayButton(BuildContext context, SlideshowAction action) {
return 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),
),
),
);
}
Widget _buildTvButtonRowContent(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
textDirection: ViewerBottomOverlay.actionsDirection,
children: _actions.map((action) {
return CaptionedButton(
scale: _buttonScale,
icon: action.getIcon(),
caption: action.getText(context),
onPressed: () => _onAction(context, action),
);
}).toList(),
); );
} }