feat(desktop): improve snackbar behavior
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'layout_constants.dart';
|
||||
import 'platform_detector.dart';
|
||||
|
||||
/// Global key for the root ScaffoldMessenger, allowing snackbars to survive navigation.
|
||||
final rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
@@ -12,19 +13,82 @@ final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
/// Types of snackbars available in the app
|
||||
enum SnackBarType { info, success, error }
|
||||
|
||||
const double _kDesktopSnackBarMaxWidth = 480.0;
|
||||
const double _kDesktopSnackBarHorizontalInset = 16.0;
|
||||
const EdgeInsets _kDismissibleSnackBarPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 14);
|
||||
|
||||
/// Utility functions for showing snackbars throughout the application
|
||||
|
||||
void showSnackBar(BuildContext context, String message, {SnackBarType type = SnackBarType.info, Duration? duration}) {
|
||||
SnackBar _buildSnackBar(
|
||||
BuildContext context,
|
||||
ScaffoldMessengerState messenger, {
|
||||
required Widget content,
|
||||
required Color? backgroundColor,
|
||||
required Duration duration,
|
||||
bool? dismissible,
|
||||
}) {
|
||||
final isDesktop = PlatformDetector.isDesktopOS();
|
||||
final tapToDismiss = dismissible ?? isDesktop;
|
||||
final body = tapToDismiss
|
||||
? MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => messenger.hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss),
|
||||
child: Padding(padding: _kDismissibleSnackBarPadding, child: content),
|
||||
),
|
||||
)
|
||||
: content;
|
||||
|
||||
return SnackBar(
|
||||
content: body,
|
||||
backgroundColor: backgroundColor,
|
||||
duration: duration,
|
||||
behavior: isDesktop ? SnackBarBehavior.floating : null,
|
||||
width: isDesktop ? _desktopSnackBarWidth(context) : null,
|
||||
padding: tapToDismiss ? EdgeInsets.zero : null,
|
||||
);
|
||||
}
|
||||
|
||||
double _desktopSnackBarWidth(BuildContext context) {
|
||||
final windowWidth = MediaQuery.maybeSizeOf(context)?.width;
|
||||
if (windowWidth == null) return _kDesktopSnackBarMaxWidth;
|
||||
|
||||
final insetPadding = SnackBarTheme.of(context).insetPadding;
|
||||
final horizontalInset = insetPadding == null
|
||||
? _kDesktopSnackBarHorizontalInset * 2
|
||||
: insetPadding.left + insetPadding.right;
|
||||
final availableWidth = windowWidth - horizontalInset;
|
||||
final width = availableWidth > 0 ? availableWidth : windowWidth;
|
||||
return width < _kDesktopSnackBarMaxWidth ? width : _kDesktopSnackBarMaxWidth;
|
||||
}
|
||||
|
||||
(Color?, Duration) _snackBarStyle(SnackBarType type) => switch (type) {
|
||||
SnackBarType.info => (null, AppDurations.snackBarDefault),
|
||||
SnackBarType.success => (Colors.green, AppDurations.snackBarDefault),
|
||||
SnackBarType.error => (Colors.red, AppDurations.snackBarLong),
|
||||
};
|
||||
|
||||
void showSnackBar(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
SnackBarType type = SnackBarType.info,
|
||||
Duration? duration,
|
||||
bool? dismissible,
|
||||
}) {
|
||||
if (!context.mounted) return;
|
||||
|
||||
final (backgroundColor, defaultDuration) = switch (type) {
|
||||
SnackBarType.info => (null, AppDurations.snackBarDefault),
|
||||
SnackBarType.success => (Colors.green, AppDurations.snackBarDefault),
|
||||
SnackBarType.error => (Colors.red, AppDurations.snackBarLong),
|
||||
};
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(message), backgroundColor: backgroundColor, duration: duration ?? defaultDuration),
|
||||
final (backgroundColor, defaultDuration) = _snackBarStyle(type);
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
messenger.showSnackBar(
|
||||
_buildSnackBar(
|
||||
context,
|
||||
messenger,
|
||||
content: Text(message),
|
||||
backgroundColor: backgroundColor,
|
||||
duration: duration ?? defaultDuration,
|
||||
dismissible: dismissible,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,8 +102,16 @@ void showErrorSnackBar(BuildContext context, String message) {
|
||||
|
||||
/// Shows an error snackbar using the root ScaffoldMessenger (survives navigation).
|
||||
void showGlobalErrorSnackBar(String message) {
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(
|
||||
SnackBar(content: Text(message), backgroundColor: Colors.red, duration: AppDurations.snackBarLong),
|
||||
final messenger = rootScaffoldMessengerKey.currentState;
|
||||
if (messenger == null) return;
|
||||
messenger.showSnackBar(
|
||||
_buildSnackBar(
|
||||
messenger.context,
|
||||
messenger,
|
||||
content: Text(message),
|
||||
backgroundColor: Colors.red,
|
||||
duration: AppDurations.snackBarLong,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,9 +120,12 @@ void showGlobalErrorSnackBar(String message) {
|
||||
/// messenger when the main screen is not mounted.
|
||||
void showMainSnackBar(String message, {Duration duration = AppDurations.snackBarDefault}) {
|
||||
final messenger = mainScaffoldMessengerKey.currentState ?? rootScaffoldMessengerKey.currentState;
|
||||
if (messenger == null) return;
|
||||
messenger
|
||||
?..removeCurrentSnackBar()
|
||||
..showSnackBar(SnackBar(content: Text(message), duration: duration));
|
||||
..removeCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
_buildSnackBar(messenger.context, messenger, content: Text(message), backgroundColor: null, duration: duration),
|
||||
);
|
||||
}
|
||||
|
||||
void showSuccessSnackBar(BuildContext context, String message) {
|
||||
|
||||
Reference in New Issue
Block a user