viewer: fixed reappearing swiped snack bar

This commit is contained in:
Thibault Deckers 2022-05-24 19:31:47 +09:00
parent 7e11a397c6
commit 234c8aa21e
3 changed files with 146 additions and 9 deletions

View file

@ -7,6 +7,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/accessibility_service.dart'; 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/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/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves/widgets/viewer/entry_viewer_page.dart';
@ -58,24 +59,22 @@ mixin FeedbackMixin {
(context) => SafeArea( (context) => SafeArea(
child: Padding( child: Padding(
padding: margin, padding: margin,
child: SnackBar( child: OverlaySnackBar(
content: snackBarContent, content: snackBarContent,
animation: const AlwaysStoppedAnimation<double>(1),
action: action != null action: action != null
? SnackBarAction( ? TextButton(
label: action.label, style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Theme.of(context).snackBarTheme.actionTextColor),
),
onPressed: () { onPressed: () {
// the regular snack bar dismiss behavior is confused
// because it expects a `Scaffold` in context,
// so we manually dimiss the overlay entry
// TODO TLAD [bug] after dismissing the overlay, tapping on the snack bar area makes the overlay visible again
notificationOverlayEntry?.dismiss(); notificationOverlayEntry?.dismiss();
action.onPressed(); action.onPressed();
}, },
child: Text(action.label),
) )
: null, : null,
duration: duration,
dismissDirection: DismissDirection.horizontal, dismissDirection: DismissDirection.horizontal,
onDismiss: () => notificationOverlayEntry?.dismiss(),
), ),
), ),
), ),

View file

@ -0,0 +1,135 @@
import 'package:flutter/material.dart';
// adapted from Flutter `SnackBar` in `/material/snack_bar.dart`
// As of Flutter v3.0.1, `SnackBar` is not customizable enough to add margin
// and ignore pointers in that area, so we use an overlay entry instead.
// This overlay entry is not below a `Scaffold` (which is expected by `SnackBar`
// and `SnackBarAction`), and is not dismissed the same way.
// This adaptation assumes the `SnackBarBehavior.floating` behavior and no animation.
class OverlaySnackBar extends StatelessWidget {
final Widget content;
final Widget? action;
final DismissDirection dismissDirection;
final VoidCallback onDismiss;
const OverlaySnackBar({
super.key,
required this.content,
required this.action,
required this.dismissDirection,
required this.onDismiss,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final snackBarTheme = theme.snackBarTheme;
final isThemeDark = theme.brightness == Brightness.dark;
final buttonColor = isThemeDark ? colorScheme.primary : colorScheme.secondary;
final brightness = isThemeDark ? Brightness.light : Brightness.dark;
final themeBackgroundColor = isThemeDark ? colorScheme.onSurface : Color.alphaBlend(colorScheme.onSurface.withOpacity(0.80), colorScheme.surface);
final inverseTheme = theme.copyWith(
colorScheme: ColorScheme(
primary: colorScheme.onPrimary,
secondary: buttonColor,
surface: colorScheme.onSurface,
background: themeBackgroundColor,
error: colorScheme.onError,
onPrimary: colorScheme.primary,
onSecondary: colorScheme.secondary,
onSurface: colorScheme.surface,
onBackground: colorScheme.background,
onError: colorScheme.error,
brightness: brightness,
),
);
final contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.subtitle1;
const horizontalPadding = 16.0;
final padding = EdgeInsetsDirectional.only(start: horizontalPadding, end: action != null ? 0 : horizontalPadding);
const actionHorizontalMargin = horizontalPadding / 2;
const singleLineVerticalPadding = 14.0;
Widget snackBar = Padding(
padding: padding,
child: Row(
children: <Widget>[
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: singleLineVerticalPadding),
child: DefaultTextStyle(
style: contentTextStyle!,
child: content,
),
),
),
if (action != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: actionHorizontalMargin),
child: TextButtonTheme(
data: TextButtonThemeData(
style: TextButton.styleFrom(
primary: buttonColor,
padding: const EdgeInsets.symmetric(horizontal: horizontalPadding),
),
),
child: action!,
),
),
],
),
);
final elevation = snackBarTheme.elevation ?? 6.0;
final backgroundColor = snackBarTheme.backgroundColor ?? inverseTheme.colorScheme.background;
final shape = snackBarTheme.shape ?? const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
snackBar = Material(
shape: shape,
elevation: elevation,
color: backgroundColor,
child: Theme(
data: inverseTheme,
child: snackBar,
),
);
const topMargin = 5.0;
const bottomMargin = 10.0;
const horizontalMargin = 15.0;
snackBar = Padding(
padding: const EdgeInsets.fromLTRB(
horizontalMargin,
topMargin,
horizontalMargin,
bottomMargin,
),
child: snackBar,
);
snackBar = SafeArea(
top: false,
bottom: false,
child: snackBar,
);
snackBar = Semantics(
container: true,
liveRegion: true,
onDismiss: onDismiss,
child: Dismissible(
key: const Key('dismissible'),
direction: dismissDirection,
resizeDuration: null,
onDismissed: (direction) => onDismiss(),
child: snackBar,
),
);
return snackBar;
}
}

View file

@ -146,6 +146,9 @@ flutter:
# `OutputBuffer` in `/services/common/output_buffer.dart` # `OutputBuffer` in `/services/common/output_buffer.dart`
# adapts from Flutter `_OutputBuffer` in `/foundation/consolidate_response.dart` # adapts from Flutter `_OutputBuffer` in `/foundation/consolidate_response.dart`
# #
# `OverlaySnackBar` in `/widgets/common/action_mixins/overlay_snack_bar.dart`
# adapts from Flutter `SnackBar` in `/material/snack_bar.dart`
#
# `EagerScaleGestureRecognizer` in `/widgets/common/behaviour/eager_scale_gesture_recognizer.dart` # `EagerScaleGestureRecognizer` in `/widgets/common/behaviour/eager_scale_gesture_recognizer.dart`
# adapts from Flutter `ScaleGestureRecognizer` in `/gestures/scale.dart` # adapts from Flutter `ScaleGestureRecognizer` in `/gestures/scale.dart`
# #