// Author: fruit.com.cn import 'dart:async'; import 'package:flutter/material.dart'; const Key _toastKey = Key('centered-toast'); const Duration _fadeDuration = Duration(milliseconds: 200); const Duration _visibleDuration = Duration(seconds: 2); const double _toastRadius = 12; const double _toastMaxWidth = 320; const double _toastHorizontalInset = 20; const double _toastVerticalInset = 12; const double _toastBackgroundOpacity = 0.78; const TextStyle _toastTextStyle = TextStyle( color: Colors.white, fontSize: 14, height: 1.4, fontWeight: FontWeight.w500, ); const EdgeInsets _toastPadding = EdgeInsets.symmetric( horizontal: _toastHorizontalInset, vertical: _toastVerticalInset, ); OverlayEntry? _activeToast; /// Shows a lightweight centered message and replaces any active toast. void showCenteredToast(BuildContext context, String message) { _removeActiveToast(); final overlay = Overlay.of(context, rootOverlay: true); late final OverlayEntry entry; entry = OverlayEntry( builder: (_) => _CenteredToast( message: message, onDismissed: () { if (!identical(_activeToast, entry)) return; _activeToast = null; if (entry.mounted) entry.remove(); }, ), ); _activeToast = entry; overlay.insert(entry); } void _removeActiveToast() { final entry = _activeToast; _activeToast = null; if (entry != null && entry.mounted) entry.remove(); } class _CenteredToast extends StatefulWidget { const _CenteredToast({required this.message, required this.onDismissed}); final String message; final VoidCallback onDismissed; @override State<_CenteredToast> createState() => _CenteredToastState(); } class _CenteredToastState extends State<_CenteredToast> with SingleTickerProviderStateMixin { late final AnimationController _opacity = AnimationController( vsync: this, duration: _fadeDuration, ); Timer? _dismissTimer; @override void initState() { super.initState(); _opacity.forward(); _dismissTimer = Timer(_visibleDuration, _dismiss); } Future _dismiss() async { try { await _opacity.reverse(); } on TickerCanceled { return; } if (mounted) widget.onDismissed(); } @override void dispose() { _dismissTimer?.cancel(); _opacity.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Positioned.fill( child: IgnorePointer( child: Center( child: FadeTransition( opacity: CurvedAnimation( parent: _opacity, curve: Curves.easeOutCubic, ), child: Material( type: MaterialType.transparency, child: Container( key: _toastKey, constraints: const BoxConstraints(maxWidth: _toastMaxWidth), padding: _toastPadding, decoration: BoxDecoration( color: Colors.black.withValues( alpha: _toastBackgroundOpacity, ), borderRadius: BorderRadius.circular(_toastRadius), ), child: Text( widget.message, textAlign: TextAlign.center, style: _toastTextStyle, ), ), ), ), ), ), ); } }