mirror of
https://github.com/inventree/inventree-app.git
synced 2026-05-20 22:56:36 +00:00
206 lines
5.4 KiB
Dart
206 lines
5.4 KiB
Dart
import "dart:async";
|
|
|
|
import "package:flutter/material.dart";
|
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
|
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 top of the screen
|
|
*/
|
|
void showSnackIcon(
|
|
String text, {
|
|
IconData? icon,
|
|
Function()? onAction,
|
|
bool? success,
|
|
String? actionText,
|
|
}) {
|
|
debug("showSnackIcon: '${text}'");
|
|
|
|
// Escape quickly if we do not have context
|
|
if (!hasContext()) {
|
|
return;
|
|
}
|
|
|
|
BuildContext? context = OneContext().context;
|
|
if (context == null) return;
|
|
|
|
// Dismiss any currently visible snack immediately
|
|
_currentSnackOverlay?.remove();
|
|
_currentSnackOverlay = null;
|
|
|
|
Color backgroundColor = Colors.deepOrange;
|
|
|
|
if (success != null && success == true) {
|
|
backgroundColor = Colors.lightGreen;
|
|
|
|
if (icon == null && onAction == null) {
|
|
icon = TablerIcons.circle_check;
|
|
}
|
|
} else if (success != null && success == false) {
|
|
backgroundColor = Colors.deepOrange;
|
|
|
|
if (icon == null && onAction == null) {
|
|
icon = TablerIcons.exclamation_circle;
|
|
}
|
|
}
|
|
|
|
final String actionLabel = actionText ?? L10().details;
|
|
final Duration duration = Duration(seconds: onAction == null ? 5 : 10);
|
|
|
|
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;
|
|
}
|
|
},
|
|
),
|
|
);
|
|
|
|
_currentSnackOverlay = entry;
|
|
Overlay.of(context).insert(entry);
|
|
}
|
|
|
|
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),
|
|
),
|
|
),
|
|
if (widget.icon != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 8),
|
|
child: Icon(widget.icon, color: Colors.white),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (widget.onAction != null)
|
|
TextButton(
|
|
onPressed: () {
|
|
_dismiss();
|
|
widget.onAction!();
|
|
},
|
|
child: Text(
|
|
widget.actionText ?? "",
|
|
style: const TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.close, color: Colors.white),
|
|
onPressed: _dismiss,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|