You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
5.0 KiB

2 years ago
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. enum Toast {
  4. short,
  5. long,
  6. }
  7. enum ToastGravity {
  8. top,
  9. bottom,
  10. center,
  11. topLeft,
  12. topRight,
  13. bottomLeft,
  14. bottomRight,
  15. centerLEft,
  16. centerRight,
  17. snackBar,
  18. }
  19. typedef PositionedToastBuilder = Widget Function(BuildContext context, Widget child);
  20. class FToast {
  21. late BuildContext context;
  22. static final FToast _instance = FToast._internal();
  23. factory FToast() {
  24. return _instance;
  25. }
  26. FToast init(BuildContext context) {
  27. _instance.context = context;
  28. return _instance;
  29. }
  30. FToast._internal();
  31. OverlayEntry? _entry;
  32. final List<_ToastEntry> _overlayQueue = [];
  33. Timer? _timer;
  34. void _showOverlay() {
  35. if (_overlayQueue.isEmpty) {
  36. _entry = null;
  37. return;
  38. }
  39. _ToastEntry toastEntry = _overlayQueue.removeAt(0);
  40. _entry = toastEntry.entry;
  41. Overlay.of(context).insert(_entry!);
  42. _timer = Timer(toastEntry.duration, () {
  43. Future.delayed(const Duration(milliseconds: 360), () {
  44. removeCustomToast();
  45. });
  46. });
  47. }
  48. void removeCustomToast() {
  49. _timer!.cancel();
  50. _timer = null;
  51. if (_entry != null) _entry!.remove();
  52. _entry = null;
  53. _showOverlay();
  54. }
  55. void removeQueuedCustomToasts() {
  56. _timer?.cancel();
  57. _timer = null;
  58. _overlayQueue.clear();
  59. if (_entry != null) _entry!.remove();
  60. _entry = null;
  61. }
  62. void showToast({
  63. required Widget child,
  64. PositionedToastBuilder? positionedToastBuilder,
  65. required Duration? toastDuration,
  66. required ToastGravity gravity,
  67. int fadeDuration = 350,
  68. }) {
  69. Widget newChild = _ToastStateFul(child, toastDuration ?? const Duration(seconds: 2), fadeDuration: fadeDuration);
  70. if (gravity == ToastGravity.bottom) {
  71. if (MediaQuery.of(context).viewInsets.bottom != 0) {
  72. gravity = ToastGravity.center;
  73. }
  74. }
  75. OverlayEntry newEntry = OverlayEntry(builder: (context) {
  76. if (positionedToastBuilder != null) return positionedToastBuilder(context, newChild);
  77. return _getPositionWidgetBasedOnGravity(newChild, gravity);
  78. });
  79. _overlayQueue.add(_ToastEntry(entry: newEntry, duration: toastDuration ?? const Duration(seconds: 2)));
  80. if (_timer == null) _showOverlay();
  81. }
  82. Positioned _getPositionWidgetBasedOnGravity(Widget child, ToastGravity gravity) {
  83. switch (gravity) {
  84. case ToastGravity.top:
  85. return Positioned(top: 100.0, left: 24.0, right: 24.0, child: child);
  86. case ToastGravity.topLeft:
  87. return Positioned(top: 100.0, left: 24.0, child: child);
  88. case ToastGravity.topRight:
  89. return Positioned(top: 100.0, right: 24.0, child: child);
  90. case ToastGravity.center:
  91. return Positioned(top: 50.0, bottom: 50.0, left: 24.0, right: 24.0, child: child);
  92. case ToastGravity.centerLEft:
  93. return Positioned(top: 50.0, bottom: 50.0, left: 24.0, child: child);
  94. case ToastGravity.centerRight:
  95. return Positioned(top: 50.0, bottom: 50.0, right: 24.0, child: child);
  96. case ToastGravity.bottomLeft:
  97. return Positioned(bottom: 50.0, left: 24.0, child: child);
  98. case ToastGravity.bottomRight:
  99. return Positioned(bottom: 50.0, right: 24.0, child: child);
  100. case ToastGravity.snackBar:
  101. return Positioned(bottom: MediaQuery.of(context).viewInsets.bottom, left: 0, right: 0, child: child);
  102. case ToastGravity.bottom:
  103. default:
  104. return Positioned(bottom: 50.0, left: 24.0, right: 24.0, child: child);
  105. }
  106. }
  107. }
  108. class _ToastEntry {
  109. final OverlayEntry entry;
  110. final Duration duration;
  111. _ToastEntry({required this.entry, required this.duration});
  112. }
  113. class _ToastStateFul extends StatefulWidget {
  114. const _ToastStateFul(this.child, this.duration, {Key? key, this.fadeDuration = 350}) : super(key: key);
  115. final Widget child;
  116. final Duration duration;
  117. final int fadeDuration;
  118. @override
  119. ToastStateFulState createState() => ToastStateFulState();
  120. }
  121. class ToastStateFulState extends State<_ToastStateFul> with SingleTickerProviderStateMixin {
  122. void showIt() {
  123. _animationController.forward();
  124. }
  125. void hideIt() {
  126. _animationController.reverse();
  127. _timer.cancel();
  128. }
  129. late AnimationController _animationController;
  130. late Animation _fadeAnimation;
  131. late Timer _timer;
  132. @override
  133. void initState() {
  134. _animationController = AnimationController(
  135. vsync: this,
  136. duration: Duration(milliseconds: widget.fadeDuration),
  137. );
  138. _fadeAnimation = CurvedAnimation(parent: _animationController, curve: Curves.easeIn);
  139. super.initState();
  140. showIt();
  141. _timer = Timer(widget.duration, () {
  142. hideIt();
  143. });
  144. }
  145. @override
  146. void deactivate() {
  147. _timer.cancel();
  148. _animationController.stop();
  149. super.deactivate();
  150. }
  151. @override
  152. void dispose() {
  153. _timer.cancel();
  154. _animationController.dispose();
  155. super.dispose();
  156. }
  157. @override
  158. Widget build(BuildContext context) {
  159. return FadeTransition(
  160. opacity: _fadeAnimation as Animation<double>,
  161. child: Center(
  162. child: Material(
  163. color: Colors.transparent,
  164. child: widget.child,
  165. ),
  166. ),
  167. );
  168. }
  169. }