131 lines
4.4 KiB
Dart
131 lines
4.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../navigation/profile_navigation_scope.dart';
|
|
import 'layout_constants.dart';
|
|
import 'platform_detector.dart';
|
|
|
|
/// Global key for the root ScaffoldMessenger, allowing snackbars to survive navigation.
|
|
final rootScaffoldMessengerKey = 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
|
|
|
|
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) = _snackBarStyle(type);
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
messenger.showSnackBar(
|
|
_buildSnackBar(
|
|
context,
|
|
messenger,
|
|
content: Text(message),
|
|
backgroundColor: backgroundColor,
|
|
duration: duration ?? defaultDuration,
|
|
dismissible: dismissible,
|
|
),
|
|
);
|
|
}
|
|
|
|
void showAppSnackBar(BuildContext context, String message, {Duration? duration}) {
|
|
showSnackBar(context, message, type: SnackBarType.info, duration: duration);
|
|
}
|
|
|
|
void showErrorSnackBar(BuildContext context, String message) {
|
|
showSnackBar(context, message, type: SnackBarType.error);
|
|
}
|
|
|
|
/// Shows an error snackbar using the root ScaffoldMessenger (survives navigation).
|
|
void showGlobalErrorSnackBar(String message) {
|
|
final messenger = rootScaffoldMessengerKey.currentState;
|
|
if (messenger == null) return;
|
|
messenger.showSnackBar(
|
|
_buildSnackBar(
|
|
messenger.context,
|
|
messenger,
|
|
content: Text(message),
|
|
backgroundColor: Colors.red,
|
|
duration: AppDurations.snackBarLong,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Shows an info snackbar through the main-screen messenger when available
|
|
/// (so it floats above the mobile NavigationBar), falling back to the root
|
|
/// messenger when the main screen is not mounted.
|
|
void showMainSnackBar(String message, {Duration duration = AppDurations.snackBarDefault}) {
|
|
final messenger = profileNavigationRegistry.mainScaffoldMessenger ?? rootScaffoldMessengerKey.currentState;
|
|
if (messenger == null) return;
|
|
messenger
|
|
..removeCurrentSnackBar()
|
|
..showSnackBar(
|
|
_buildSnackBar(messenger.context, messenger, content: Text(message), backgroundColor: null, duration: duration),
|
|
);
|
|
}
|
|
|
|
void showSuccessSnackBar(BuildContext context, String message) {
|
|
showSnackBar(context, message, type: SnackBarType.success);
|
|
}
|