mirror of
https://github.com/inventree/inventree-app.git
synced 2026-05-20 14:46:42 +00:00
Snackbar displays at top (#820)
- Closes https://github.com/inventree/inventree-app/issues/626
This commit is contained in:
+152
-28
@@ -1,3 +1,5 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:one_context/one_context.dart";
|
||||
@@ -5,8 +7,10 @@ import "package:one_context/one_context.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
|
||||
OverlayEntry? _currentSnackOverlay;
|
||||
|
||||
/*
|
||||
* Display a configurable 'snackbar' at the bottom of the screen
|
||||
* Display a configurable 'snackbar' at the top of the screen
|
||||
*/
|
||||
void showSnackIcon(
|
||||
String text, {
|
||||
@@ -19,23 +23,21 @@ void showSnackIcon(
|
||||
|
||||
// Escape quickly if we do not have context
|
||||
if (!hasContext()) {
|
||||
// Debug message for unit testing
|
||||
return;
|
||||
}
|
||||
|
||||
BuildContext? context = OneContext().context;
|
||||
if (context == null) return;
|
||||
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
}
|
||||
// Dismiss any currently visible snack immediately
|
||||
_currentSnackOverlay?.remove();
|
||||
_currentSnackOverlay = null;
|
||||
|
||||
Color backgroundColor = Colors.deepOrange;
|
||||
|
||||
// Make some selections based on the "success" value
|
||||
if (success != null && success == true) {
|
||||
backgroundColor = Colors.lightGreen;
|
||||
|
||||
// Select an icon if we do not have an action
|
||||
if (icon == null && onAction == null) {
|
||||
icon = TablerIcons.circle_check;
|
||||
}
|
||||
@@ -47,35 +49,157 @@ void showSnackIcon(
|
||||
}
|
||||
}
|
||||
|
||||
String _action = actionText ?? L10().details;
|
||||
final String actionLabel = actionText ?? L10().details;
|
||||
final Duration duration = Duration(seconds: onAction == null ? 5 : 10);
|
||||
|
||||
List<Widget> childs = [Text(text), Spacer()];
|
||||
late OverlayEntry entry;
|
||||
entry = OverlayEntry(
|
||||
builder: (ctx) => _TopSnackBar(
|
||||
text: text,
|
||||
icon: icon,
|
||||
backgroundColor: backgroundColor,
|
||||
onAction: onAction,
|
||||
actionText: actionLabel,
|
||||
duration: duration,
|
||||
onDismiss: () {
|
||||
entry.remove();
|
||||
if (_currentSnackOverlay == entry) {
|
||||
_currentSnackOverlay = null;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (icon != null) {
|
||||
childs.add(Icon(icon));
|
||||
_currentSnackOverlay = entry;
|
||||
Overlay.of(context).insert(entry);
|
||||
}
|
||||
|
||||
OneContext().showSnackBar(
|
||||
builder: (context) => SnackBar(
|
||||
content: GestureDetector(
|
||||
child: Row(children: childs),
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context!).hideCurrentSnackBar();
|
||||
},
|
||||
class _TopSnackBar extends StatefulWidget {
|
||||
const _TopSnackBar({
|
||||
required this.text,
|
||||
required this.backgroundColor,
|
||||
required this.duration,
|
||||
required this.onDismiss,
|
||||
this.icon,
|
||||
this.onAction,
|
||||
this.actionText,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final IconData? icon;
|
||||
final Color backgroundColor;
|
||||
final Function()? onAction;
|
||||
final String? actionText;
|
||||
final Duration duration;
|
||||
final VoidCallback onDismiss;
|
||||
|
||||
@override
|
||||
_TopSnackBarState createState() => _TopSnackBarState();
|
||||
}
|
||||
|
||||
class _TopSnackBarState extends State<_TopSnackBar>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<Offset> _slideAnimation;
|
||||
Timer? _timer;
|
||||
bool _dismissed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: const Offset(0, -1),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
|
||||
|
||||
_controller.forward();
|
||||
_timer = Timer(widget.duration, _dismiss);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _dismiss() {
|
||||
if (_dismissed) return;
|
||||
_dismissed = true;
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
_controller.reverse().then((_) {
|
||||
if (mounted) widget.onDismiss();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: ColoredBox(
|
||||
color: widget.backgroundColor,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: _dismiss,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.text,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
showCloseIcon: true,
|
||||
action: onAction == null
|
||||
? null
|
||||
: SnackBarAction(
|
||||
label: _action,
|
||||
),
|
||||
if (widget.icon != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: Icon(widget.icon, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.onAction != null)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Immediately dismiss the notification
|
||||
ScaffoldMessenger.of(context!).hideCurrentSnackBar();
|
||||
onAction();
|
||||
_dismiss();
|
||||
widget.onAction!();
|
||||
},
|
||||
child: Text(
|
||||
widget.actionText ?? "",
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.white),
|
||||
onPressed: _dismiss,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
duration: Duration(seconds: onAction == null ? 5 : 10),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user