diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ee1b1c5f..b8e972e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ All notable changes to this project will be documented in this file. ### Changed - upgraded Flutter to stable v2.10.4 +- snack bars are dismissible with an horizontal swipe instead of a down swipe +- Viewer: snack bars avoid quick actions and thumbnails at the bottom ### Fixed diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index 1b81ebd50..8ac7c3587 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -8,9 +8,11 @@ import 'package:aves/services/accessibility_service.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:overlay_support/overlay_support.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; import 'package:provider/provider.dart'; @@ -34,16 +36,45 @@ mixin FeedbackMixin { // provide the messenger if feedback happens as the widget is disposed void showFeedbackWithMessenger(BuildContext context, ScaffoldMessengerState messenger, String message, [SnackBarAction? action]) { _getSnackBarDuration(action != null).then((duration) { - final progressColor = Theme.of(context).colorScheme.secondary; - messenger.showSnackBar(SnackBar( - content: _FeedbackMessage( - message: message, - progressColor: progressColor, - duration: action != null ? duration : null, - ), - action: action, - duration: duration, - )); + final snackBarContent = _FeedbackMessage( + message: message, + progressColor: Theme.of(context).colorScheme.secondary, + duration: action != null ? duration : null, + ); + + if (context.currentRouteName == EntryViewerPage.routeName) { + // 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, + // and space under the snack bar `margin` does not receive gestures + // (because it is used by the `Dismissible` wrapping the snack bar) + // so we use `showOverlayNotification` instead + showOverlayNotification( + (context) => SafeArea( + child: Padding( + padding: margin, + child: SnackBar( + content: snackBarContent, + animation: const AlwaysStoppedAnimation(1), + action: action, + duration: duration, + dismissDirection: DismissDirection.horizontal, + ), + ), + ), + duration: duration, + position: NotificationPosition.bottom, + context: context, + ); + } else { + messenger.showSnackBar(SnackBar( + content: snackBarContent, + action: action, + duration: duration, + dismissDirection: DismissDirection.horizontal, + )); + } }); } diff --git a/lib/widgets/common/thumbnail/scroller.dart b/lib/widgets/common/thumbnail/scroller.dart index c0bf6e344..a74e32bf5 100644 --- a/lib/widgets/common/thumbnail/scroller.dart +++ b/lib/widgets/common/thumbnail/scroller.dart @@ -32,6 +32,8 @@ class ThumbnailScroller extends StatefulWidget { @override State createState() => _ThumbnailScrollerState(); + + static double get preferredHeight => _ThumbnailScrollerState.thumbnailExtent; } class _ThumbnailScrollerState extends State { diff --git a/lib/widgets/viewer/entry_viewer_page.dart b/lib/widgets/viewer/entry_viewer_page.dart index 16b8f7d31..8caf6e5cd 100644 --- a/lib/widgets/viewer/entry_viewer_page.dart +++ b/lib/widgets/viewer/entry_viewer_page.dart @@ -4,6 +4,7 @@ import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/viewer/entry_viewer_stack.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; +import 'package:aves/widgets/viewer/overlay/bottom.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; import 'package:aves/widgets/viewer/visual/conductor.dart'; import 'package:flutter/material.dart'; @@ -44,6 +45,10 @@ class EntryViewerPage extends StatelessWidget { ), ); } + + static EdgeInsets snackBarMargin(BuildContext context) { + return EdgeInsets.only(bottom: ViewerBottomOverlay.actionSafeHeight(context)); + } } class ViewStateConductorProvider extends StatelessWidget { diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom.dart index 6fc54c51f..39d016c8e 100644 --- a/lib/widgets/viewer/overlay/bottom.dart +++ b/lib/widgets/viewer/overlay/bottom.dart @@ -33,6 +33,10 @@ class ViewerBottomOverlay extends StatefulWidget { @override State createState() => _ViewerBottomOverlayState(); + + static double actionSafeHeight(BuildContext context) { + return ViewerButtonRow.preferredHeight(context) + (settings.showOverlayThumbnailPreview ? ViewerThumbnailPreview.preferredHeight : 0); + } } class _ViewerBottomOverlayState extends State { diff --git a/lib/widgets/viewer/overlay/thumbnail_preview.dart b/lib/widgets/viewer/overlay/thumbnail_preview.dart index 4218fd089..796a1b99b 100644 --- a/lib/widgets/viewer/overlay/thumbnail_preview.dart +++ b/lib/widgets/viewer/overlay/thumbnail_preview.dart @@ -19,6 +19,8 @@ class ViewerThumbnailPreview extends StatefulWidget { @override State createState() => _ViewerThumbnailPreviewState(); + + static double get preferredHeight => ThumbnailScroller.preferredHeight; } class _ViewerThumbnailPreviewState extends State { diff --git a/lib/widgets/viewer/overlay/viewer_button_row.dart b/lib/widgets/viewer/overlay/viewer_button_row.dart index a13158e12..db1f6a81c 100644 --- a/lib/widgets/viewer/overlay/viewer_button_row.dart +++ b/lib/widgets/viewer/overlay/viewer_button_row.dart @@ -31,6 +31,10 @@ class ViewerButtonRow extends StatelessWidget { static const double outerPadding = 8; static const double innerPadding = 8; + static double preferredHeight(BuildContext context) => _buttonSize(context) + ViewerButtonRowContent.padding; + + static double _buttonSize(BuildContext context) => OverlayButton.getSize(context); + const ViewerButtonRow({ Key? key, required this.mainEntry, @@ -107,7 +111,7 @@ class ViewerButtonRow extends StatelessWidget { bottom: false, child: LayoutBuilder( builder: (context, constraints) { - final buttonWidth = OverlayButton.getSize(context); + final buttonWidth = _buttonSize(context); final availableCount = ((constraints.maxWidth - outerPadding * 2) / (buttonWidth + innerPadding)).floor(); return Selector( selector: (context, s) => s.isRotationLocked,