import 'package:flutter/material.dart'; import '../../../core/theme/design_tokens.dart'; import 'toast_type.dart'; import 'toast_type_config.dart'; class Toast { static void show( BuildContext context, String message, { ToastType type = ToastType.info, Duration duration = const Duration(seconds: 2), }) { final overlay = Overlay.of(context); late OverlayEntry entry; entry = OverlayEntry( builder: (context) => _ToastWidget( message: message, type: type, duration: duration, onDismiss: () => entry.remove(), ), ); overlay.insert(entry); } } class _ToastWidget extends StatefulWidget { final String message; final ToastType type; final Duration duration; final VoidCallback onDismiss; const _ToastWidget({ required this.message, required this.type, required this.duration, required this.onDismiss, }); @override State<_ToastWidget> createState() => _ToastWidgetState(); } class _ToastWidgetState extends State<_ToastWidget> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _slideAnimation; late Animation _fadeAnimation; bool _dismissed = false; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 280), vsync: this, ); _slideAnimation = Tween( begin: const Offset(0, -0.18), end: Offset.zero, ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic)); _fadeAnimation = Tween( begin: 0, end: 1, ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); _controller.forward(); Future.delayed(widget.duration, _dismiss); } void _dismiss() { if (!mounted || _dismissed) return; _dismissed = true; _controller.reverse().then((_) { if (mounted) { widget.onDismiss(); } }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final config = ToastTypeConfig.fromType(context, widget.type); final colorScheme = Theme.of(context).colorScheme; return Positioned( top: MediaQuery.of(context).padding.top + 12, left: 16, right: 16, child: SlideTransition( position: _slideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Material( color: Colors.transparent, child: SafeArea( bottom: false, child: GestureDetector( onTap: _dismiss, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: config.surfaceColor, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all(color: config.borderColor), boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: 0.08), blurRadius: 24, offset: const Offset(0, 10), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 34, height: 34, decoration: BoxDecoration( color: config.iconColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(AppRadius.full), ), child: Icon( config.icon, size: 18, color: config.iconColor, ), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( config.label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: config.textColor, ), ), const SizedBox(height: 2), Text( widget.message, style: TextStyle( fontSize: 14, height: 1.35, color: config.textColor, ), ), ], ), ), const SizedBox(width: 8), Icon( Icons.close_rounded, size: 18, color: config.textColor.withValues(alpha: 0.72), ), ], ), ), ), ), ), ), ), ); } }