viewer: fixed snack bar margin on page transition
This commit is contained in:
parent
9f9cdebc9e
commit
034a816704
2 changed files with 62 additions and 12 deletions
|
@ -11,8 +11,8 @@ import 'package:aves/widgets/common/action_mixins/overlay_snack_bar.dart';
|
||||||
import 'package:aves/widgets/common/basic/circle.dart';
|
import 'package:aves/widgets/common/basic/circle.dart';
|
||||||
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||||
import 'package:aves/widgets/common/extensions/theme.dart';
|
import 'package:aves/widgets/common/extensions/theme.dart';
|
||||||
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:overlay_support/overlay_support.dart';
|
import 'package:overlay_support/overlay_support.dart';
|
||||||
|
@ -22,6 +22,13 @@ import 'package:provider/provider.dart';
|
||||||
enum FeedbackType { info, warn }
|
enum FeedbackType { info, warn }
|
||||||
|
|
||||||
mixin FeedbackMixin {
|
mixin FeedbackMixin {
|
||||||
|
static final ValueNotifier<EdgeInsets?> snackBarMarginOverrideNotifier = ValueNotifier(null);
|
||||||
|
|
||||||
|
static EdgeInsets snackBarMarginDefault(BuildContext context) {
|
||||||
|
final mq = context.read<MediaQueryData>();
|
||||||
|
return EdgeInsets.only(bottom: max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom));
|
||||||
|
}
|
||||||
|
|
||||||
void dismissFeedback(BuildContext context) => ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
void dismissFeedback(BuildContext context) => ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||||
|
|
||||||
void showFeedback(BuildContext context, FeedbackType type, String message, [SnackBarAction? action]) {
|
void showFeedback(BuildContext context, FeedbackType type, String message, [SnackBarAction? action]) {
|
||||||
|
@ -53,10 +60,7 @@ mixin FeedbackMixin {
|
||||||
stop: action != null ? start.add(duration) : null,
|
stop: action != null ? start.add(duration) : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context.currentRouteName == EntryViewerPage.routeName) {
|
if (snackBarMarginOverrideNotifier.value != null) {
|
||||||
// avoid interactive widgets at the bottom of the page
|
|
||||||
final margin = EntryViewerPage.snackBarMargin(context);
|
|
||||||
|
|
||||||
// as of Flutter v2.10.4, `SnackBar` can only be positioned at the bottom,
|
// as of Flutter v2.10.4, `SnackBar` can only be positioned at the bottom,
|
||||||
// and space under the snack bar `margin` does not receive gestures
|
// and space under the snack bar `margin` does not receive gestures
|
||||||
// (because it is used by the `Dismissible` wrapping the snack bar)
|
// (because it is used by the `Dismissible` wrapping the snack bar)
|
||||||
|
@ -65,8 +69,15 @@ mixin FeedbackMixin {
|
||||||
notificationOverlayEntry = showOverlayNotification(
|
notificationOverlayEntry = showOverlayNotification(
|
||||||
(context) => SafeArea(
|
(context) => SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Padding(
|
child: ValueListenableBuilder<EdgeInsets?>(
|
||||||
padding: margin,
|
valueListenable: snackBarMarginOverrideNotifier,
|
||||||
|
builder: (context, margin, child) {
|
||||||
|
return AnimatedPadding(
|
||||||
|
padding: margin ?? snackBarMarginDefault(context),
|
||||||
|
duration: ADurations.pageTransitionAnimation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: OverlaySnackBar(
|
child: OverlaySnackBar(
|
||||||
content: snackBarContent,
|
content: snackBarContent,
|
||||||
action: action != null
|
action: action != null
|
||||||
|
@ -346,6 +357,7 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
|
||||||
final contentTextFontSize = contentTextStyle.fontSize ?? theme.textTheme.bodyMedium!.fontSize!;
|
final contentTextFontSize = contentTextStyle.fontSize ?? theme.textTheme.bodyMedium!.fontSize!;
|
||||||
final timerChangeShadowColor = colorScheme.primary;
|
final timerChangeShadowColor = colorScheme.primary;
|
||||||
|
|
||||||
|
final remainingDurationAnimation = _remainingDurationMillis;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
if (widget.type == FeedbackType.warn) ...[
|
if (widget.type == FeedbackType.warn) ...[
|
||||||
|
@ -356,16 +368,17 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
Expanded(child: Text(widget.message)),
|
Expanded(child: Text(widget.message)),
|
||||||
if (_remainingDurationMillis != null) ...[
|
if (remainingDurationAnimation != null) ...[
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: _remainingDurationMillis!,
|
animation: remainingDurationAnimation,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
final remainingDurationMillis = _remainingDurationMillis!.value;
|
final remainingDurationMillis = remainingDurationAnimation.value;
|
||||||
|
final totalDurationMillis = _totalDurationMillis;
|
||||||
return CircularIndicator(
|
return CircularIndicator(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
lineWidth: 2,
|
lineWidth: 2,
|
||||||
percent: remainingDurationMillis / _totalDurationMillis!,
|
percent: totalDurationMillis != null && totalDurationMillis > 0 ? remainingDurationMillis / totalDurationMillis : 0,
|
||||||
background: Colors.grey,
|
background: Colors.grey,
|
||||||
// progress color is provided by the caller,
|
// progress color is provided by the caller,
|
||||||
// because we cannot use the app context theme here
|
// because we cannot use the app context theme here
|
||||||
|
|
|
@ -63,7 +63,7 @@ class EntryViewerStack extends StatefulWidget {
|
||||||
State<EntryViewerStack> createState() => _EntryViewerStackState();
|
State<EntryViewerStack> createState() => _EntryViewerStackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewControllerMixin, FeedbackMixin, TickerProviderStateMixin {
|
class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewControllerMixin, FeedbackMixin, TickerProviderStateMixin, RouteAware {
|
||||||
final Floating _floating = Floating();
|
final Floating _floating = Floating();
|
||||||
late int _currentEntryIndex;
|
late int _currentEntryIndex;
|
||||||
late ValueNotifier<int> _currentVerticalPage;
|
late ValueNotifier<int> _currentVerticalPage;
|
||||||
|
@ -184,6 +184,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
AvesApp.pageRouteObserver.unsubscribe(this);
|
||||||
_floating.dispose();
|
_floating.dispose();
|
||||||
cleanEntryControllers(entryNotifier.value);
|
cleanEntryControllers(entryNotifier.value);
|
||||||
_videoActionDelegate.dispose();
|
_videoActionDelegate.dispose();
|
||||||
|
@ -287,6 +288,41 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// route aware
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
final route = ModalRoute.of(context);
|
||||||
|
if (route is PageRoute) {
|
||||||
|
AvesApp.pageRouteObserver.subscribe(this, route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPopNext() => _overrideSnackBarMargin();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPush() => _overrideSnackBarMargin();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPop() => _resetSnackBarMargin();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPushNext() => _resetSnackBarMargin();
|
||||||
|
|
||||||
|
void _overrideSnackBarMargin() {
|
||||||
|
if (isViewingImage) {
|
||||||
|
FeedbackMixin.snackBarMarginOverrideNotifier.value = EdgeInsets.only(bottom: ViewerBottomOverlay.actionSafeHeight(context));
|
||||||
|
} else {
|
||||||
|
FeedbackMixin.snackBarMarginOverrideNotifier.value = FeedbackMixin.snackBarMarginDefault(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetSnackBarMargin() => FeedbackMixin.snackBarMarginOverrideNotifier.value = null;
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
|
||||||
void _onAppLifecycleStateChanged() {
|
void _onAppLifecycleStateChanged() {
|
||||||
switch (AvesApp.lifecycleStateNotifier.value) {
|
switch (AvesApp.lifecycleStateNotifier.value) {
|
||||||
case AppLifecycleState.inactive:
|
case AppLifecycleState.inactive:
|
||||||
|
@ -662,6 +698,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
|
|
||||||
void _onVerticalPageChanged(int page) {
|
void _onVerticalPageChanged(int page) {
|
||||||
_currentVerticalPage.value = page;
|
_currentVerticalPage.value = page;
|
||||||
|
_overrideSnackBarMargin();
|
||||||
switch (page) {
|
switch (page) {
|
||||||
case transitionPage:
|
case transitionPage:
|
||||||
dismissFeedback(context);
|
dismissFeedback(context);
|
||||||
|
|
Loading…
Reference in a new issue