#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
- 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

View file

@ -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<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
State<ThumbnailScroller> createState() => _ThumbnailScrollerState();
static double get preferredHeight => _ThumbnailScrollerState.thumbnailExtent;
}
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/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 {

View file

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

View file

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

View file

@ -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<Settings, bool>(
selector: (context, s) => s.isRotationLocked,