#218 viewer: snack bars avoid interactive widgets at the bottom

This commit is contained in:
Thibault Deckers 2022-04-12 19:27:52 +09:00
parent 2a0b9984d8
commit eb4af53f5c
7 changed files with 61 additions and 11 deletions

View file

@ -15,6 +15,8 @@ All notable changes to this project will be documented in this file.
### Changed ### Changed
- upgraded Flutter to stable v2.10.4 - 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 ### Fixed

View file

@ -8,9 +8,11 @@ import 'package:aves/services/accessibility_service.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.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/foundation.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:percent_indicator/circular_percent_indicator.dart'; import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -34,16 +36,45 @@ mixin FeedbackMixin {
// provide the messenger if feedback happens as the widget is disposed // provide the messenger if feedback happens as the widget is disposed
void showFeedbackWithMessenger(BuildContext context, ScaffoldMessengerState messenger, String message, [SnackBarAction? action]) { void showFeedbackWithMessenger(BuildContext context, ScaffoldMessengerState messenger, String message, [SnackBarAction? action]) {
_getSnackBarDuration(action != null).then((duration) { _getSnackBarDuration(action != null).then((duration) {
final progressColor = Theme.of(context).colorScheme.secondary; final snackBarContent = _FeedbackMessage(
messenger.showSnackBar(SnackBar( message: message,
content: _FeedbackMessage( progressColor: Theme.of(context).colorScheme.secondary,
message: message, duration: action != null ? duration : null,
progressColor: progressColor, );
duration: action != null ? duration : null,
), if (context.currentRouteName == EntryViewerPage.routeName) {
action: action, // avoid interactive widgets at the bottom of the page
duration: duration, 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<double>(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,
));
}
}); });
} }

View file

@ -32,6 +32,8 @@ class ThumbnailScroller extends StatefulWidget {
@override @override
State<ThumbnailScroller> createState() => _ThumbnailScrollerState(); State<ThumbnailScroller> createState() => _ThumbnailScrollerState();
static double get preferredHeight => _ThumbnailScrollerState.thumbnailExtent;
} }
class _ThumbnailScrollerState extends State<ThumbnailScroller> { class _ThumbnailScrollerState extends State<ThumbnailScroller> {

View file

@ -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/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/viewer/entry_viewer_stack.dart'; import 'package:aves/widgets/viewer/entry_viewer_stack.dart';
import 'package:aves/widgets/viewer/multipage/conductor.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/video/conductor.dart';
import 'package:aves/widgets/viewer/visual/conductor.dart'; import 'package:aves/widgets/viewer/visual/conductor.dart';
import 'package:flutter/material.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 { class ViewStateConductorProvider extends StatelessWidget {

View file

@ -33,6 +33,10 @@ class ViewerBottomOverlay extends StatefulWidget {
@override @override
State<StatefulWidget> createState() => _ViewerBottomOverlayState(); State<StatefulWidget> createState() => _ViewerBottomOverlayState();
static double actionSafeHeight(BuildContext context) {
return ViewerButtonRow.preferredHeight(context) + (settings.showOverlayThumbnailPreview ? ViewerThumbnailPreview.preferredHeight : 0);
}
} }
class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> { class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> {

View file

@ -19,6 +19,8 @@ class ViewerThumbnailPreview extends StatefulWidget {
@override @override
State<ViewerThumbnailPreview> createState() => _ViewerThumbnailPreviewState(); State<ViewerThumbnailPreview> createState() => _ViewerThumbnailPreviewState();
static double get preferredHeight => ThumbnailScroller.preferredHeight;
} }
class _ViewerThumbnailPreviewState extends State<ViewerThumbnailPreview> { class _ViewerThumbnailPreviewState extends State<ViewerThumbnailPreview> {

View file

@ -31,6 +31,10 @@ class ViewerButtonRow extends StatelessWidget {
static const double outerPadding = 8; static const double outerPadding = 8;
static const double innerPadding = 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({ const ViewerButtonRow({
Key? key, Key? key,
required this.mainEntry, required this.mainEntry,
@ -107,7 +111,7 @@ class ViewerButtonRow extends StatelessWidget {
bottom: false, bottom: false,
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final buttonWidth = OverlayButton.getSize(context); final buttonWidth = _buttonSize(context);
final availableCount = ((constraints.maxWidth - outerPadding * 2) / (buttonWidth + innerPadding)).floor(); final availableCount = ((constraints.maxWidth - outerPadding * 2) / (buttonWidth + innerPadding)).floor();
return Selector<Settings, bool>( return Selector<Settings, bool>(
selector: (context, s) => s.isRotationLocked, selector: (context, s) => s.isRotationLocked,