#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) {
return SizedBox.fromSize(
size: availableSize,
child: Align(
alignment: AlignmentDirectional.bottomEnd,
child: TooltipTheme(
return TooltipTheme(
data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: SlideshowButtons(
child: Align(
alignment: AlignmentDirectional.bottomEnd,
child: SlideshowBottomOverlay(
animationController: _overlayAnimationController,
),
availableSize: availableSize,
viewInsets: _frozenViewInsets,
viewPadding: _frozenViewPadding,
),
),
);

View file

@ -21,7 +21,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
class ViewerBottomOverlay extends StatefulWidget {
class ViewerBottomOverlay extends StatelessWidget {
final List<AvesEntry> entries;
final int index;
final CollectionLens? collection;
@ -33,6 +33,10 @@ class ViewerBottomOverlay extends StatefulWidget {
// always keep action buttons in the lower right corner, even with RTL locales
static const actionsDirection = TextDirection.ltr;
AvesEntry? get entry {
return index < entries.length ? entries[index] : null;
}
const ViewerBottomOverlay({
super.key,
required this.entries,
@ -45,27 +49,6 @@ class ViewerBottomOverlay extends StatefulWidget {
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
Widget build(BuildContext context) {
final mainEntry = entry;
@ -73,15 +56,15 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
Widget _buildContent({AvesEntry? pageEntry}) => _BottomOverlayContent(
entries: entries,
index: widget.index,
index: index,
mainEntry: mainEntry,
pageEntry: pageEntry ?? mainEntry,
collection: widget.collection,
availableSize: widget.availableSize,
viewInsets: widget.viewInsets,
viewPadding: widget.viewPadding,
collection: collection,
availableSize: availableSize,
viewInsets: viewInsets,
viewPadding: viewPadding,
multiPageController: multiPageController,
animationController: widget.animationController,
animationController: animationController,
);
Widget child = multiPageController != null
@ -102,6 +85,13 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {
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 {

View file

@ -1,21 +1,64 @@
import 'dart:math';
import 'package:aves/model/settings/settings.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/overlay_button.dart';
import 'package:aves/widgets/viewer/controls/intents.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/slideshow_page.dart';
import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.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 {
final Size availableSize;
final EdgeInsets? viewInsets, viewPadding;
final AnimationController animationController;
const SlideshowButtons({
super.key,
required this.availableSize,
required this.viewInsets,
required this.viewPadding,
required this.animationController,
});
@ -70,7 +113,8 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
@override
Widget build(BuildContext context) {
return FocusableActionDetector(
final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero);
final viewerButtonRow = FocusableActionDetector(
focusNode: _buttonRowFocusScopeNode,
shortcuts: settings.useTvLayout
? const {
@ -80,26 +124,53 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
actions: {
TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context)),
},
child: settings.useTvLayout
? Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: _actions.map((action) {
return CaptionedButton(
scale: _buttonScale,
icon: action.getIcon(),
caption: action.getText(context),
onPressed: () => _onAction(context, action),
child: SafeArea(
top: false,
bottom: false,
minimum: EdgeInsets.only(
left: viewInsetsPadding.left,
right: viewInsetsPadding.right,
),
child: _buildButtons(context),
),
);
}).toList(),
)
: SafeArea(
final availableWidth = widget.availableSize.width;
return SizedBox(
width: availableWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
viewerButtonRow,
],
),
);
}
Widget _buildButtons(BuildContext context) {
if (settings.useTvLayout) {
return _buildTvButtonRowContent(context);
}
return SafeArea(
top: false,
bottom: false,
child: Padding(
padding: const EdgeInsets.only(left: _padding / 2, right: _padding / 2, bottom: _padding),
child: Row(
mainAxisSize: MainAxisSize.min,
children: _actions
.map((action) => Padding(
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,
@ -109,11 +180,22 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
tooltip: action.getText(context),
),
),
))
.toList(),
),
),
),
);
}
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(),
);
}