viewer: fixed reappearing swiped snack bar
This commit is contained in:
parent
7e11a397c6
commit
234c8aa21e
3 changed files with 146 additions and 9 deletions
|
@ -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(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
135
lib/widgets/common/action_mixins/overlay_snack_bar.dart
Normal file
135
lib/widgets/common/action_mixins/overlay_snack_bar.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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`
|
||||||
#
|
#
|
||||||
|
|
Loading…
Reference in a new issue