import 'dart:async'; import 'package:flutter/material.dart'; enum Toast { short, long, } enum ToastGravity { top, bottom, center, topLeft, topRight, bottomLeft, bottomRight, centerLEft, centerRight, snackBar, } typedef PositionedToastBuilder = Widget Function(BuildContext context, Widget child); class FToast { late BuildContext context; static final FToast _instance = FToast._internal(); factory FToast() { return _instance; } FToast init(BuildContext context) { _instance.context = context; return _instance; } FToast._internal(); OverlayEntry? _entry; final List<_ToastEntry> _overlayQueue = []; Timer? _timer; void _showOverlay() { if (_overlayQueue.isEmpty) { _entry = null; return; } _ToastEntry toastEntry = _overlayQueue.removeAt(0); _entry = toastEntry.entry; Overlay.of(context)?.insert(_entry!); _timer = Timer(toastEntry.duration, () { Future.delayed(const Duration(milliseconds: 360), () { removeCustomToast(); }); }); } void removeCustomToast() { _timer!.cancel(); _timer = null; if (_entry != null) _entry!.remove(); _entry = null; _showOverlay(); } void removeQueuedCustomToasts() { _timer?.cancel(); _timer = null; _overlayQueue.clear(); if (_entry != null) _entry!.remove(); _entry = null; } void showToast({ required Widget child, PositionedToastBuilder? positionedToastBuilder, required Duration? toastDuration, required ToastGravity gravity, int fadeDuration = 350, }) { Widget newChild = _ToastStateFul(child, toastDuration ?? const Duration(seconds: 2), fadeDuration: fadeDuration); if (gravity == ToastGravity.bottom) { if (MediaQuery.of(context).viewInsets.bottom != 0) { gravity = ToastGravity.center; } } OverlayEntry newEntry = OverlayEntry(builder: (context) { if (positionedToastBuilder != null) return positionedToastBuilder(context, newChild); return _getPositionWidgetBasedOnGravity(newChild, gravity); }); _overlayQueue.add(_ToastEntry(entry: newEntry, duration: toastDuration ?? const Duration(seconds: 2))); if (_timer == null) _showOverlay(); } Positioned _getPositionWidgetBasedOnGravity(Widget child, ToastGravity gravity) { switch (gravity) { case ToastGravity.top: return Positioned(top: 100.0, left: 24.0, right: 24.0, child: child); case ToastGravity.topLeft: return Positioned(top: 100.0, left: 24.0, child: child); case ToastGravity.topRight: return Positioned(top: 100.0, right: 24.0, child: child); case ToastGravity.center: return Positioned(top: 50.0, bottom: 50.0, left: 24.0, right: 24.0, child: child); case ToastGravity.centerLEft: return Positioned(top: 50.0, bottom: 50.0, left: 24.0, child: child); case ToastGravity.centerRight: return Positioned(top: 50.0, bottom: 50.0, right: 24.0, child: child); case ToastGravity.bottomLeft: return Positioned(bottom: 50.0, left: 24.0, child: child); case ToastGravity.bottomRight: return Positioned(bottom: 50.0, right: 24.0, child: child); case ToastGravity.snackBar: return Positioned(bottom: MediaQuery.of(context).viewInsets.bottom, left: 0, right: 0, child: child); case ToastGravity.bottom: default: return Positioned(bottom: 50.0, left: 24.0, right: 24.0, child: child); } } } class _ToastEntry { final OverlayEntry entry; final Duration duration; _ToastEntry({required this.entry, required this.duration}); } class _ToastStateFul extends StatefulWidget { const _ToastStateFul(this.child, this.duration, {Key? key, this.fadeDuration = 350}) : super(key: key); final Widget child; final Duration duration; final int fadeDuration; @override ToastStateFulState createState() => ToastStateFulState(); } class ToastStateFulState extends State<_ToastStateFul> with SingleTickerProviderStateMixin { void showIt() { _animationController.forward(); } void hideIt() { _animationController.reverse(); _timer.cancel(); } late AnimationController _animationController; late Animation _fadeAnimation; late Timer _timer; @override void initState() { _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: widget.fadeDuration), ); _fadeAnimation = CurvedAnimation(parent: _animationController, curve: Curves.easeIn); super.initState(); showIt(); _timer = Timer(widget.duration, () { hideIt(); }); } @override void deactivate() { _timer.cancel(); _animationController.stop(); super.deactivate(); } @override void dispose() { _timer.cancel(); _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return FadeTransition( opacity: _fadeAnimation as Animation, child: Center( child: Material( color: Colors.transparent, child: widget.child, ), ), ); } }