// utils/animated_snackbar.dart import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class AnimatedSnackBar { static void show({ required BuildContext context, required String message, String? title, IconData? icon, Color backgroundColor = const Color(0xFF324563), Duration duration = const Duration(seconds: 4), SnackBarAction? action, bool showProgressBar = false, bool enableHaptic = true, Curve animationCurve = Curves.elasticOut, }) { if (enableHaptic) { HapticFeedback.lightImpact(); } final overlay = Overlay.of(context); // Create a variable to hold the overlay entry late OverlayEntry overlayEntry; overlayEntry = OverlayEntry( builder: (context) => Positioned( top: MediaQuery.of(context).viewPadding.top + 10, left: 16, right: 16, child: Material( color: Colors.transparent, child: _AnimatedSnackBarContent( message: message, title: title, icon: icon, backgroundColor: backgroundColor, duration: duration, action: action, showProgressBar: showProgressBar, animationCurve: animationCurve, onClose: () { if (overlayEntry.mounted) { overlayEntry.remove(); } }, ), ), ), ); overlay.insert(overlayEntry); // Auto remove after duration Future.delayed(duration, () { if (overlayEntry.mounted) { overlayEntry.remove(); } }); } // Quick methods for different types static void success({ required BuildContext context, required String message, String title = "Success", Color backgroundColor = const Color(0xFF059669), SnackBarAction? action, bool enableHaptic = true, }) { show( context: context, message: message, title: title, icon: Icons.check_circle_rounded, backgroundColor: backgroundColor, action: action, enableHaptic: enableHaptic, ); } static void error({ required BuildContext context, required String message, String title = "Error", Color backgroundColor = const Color(0xFFDC2626), SnackBarAction? action, bool enableHaptic = true, }) { show( context: context, message: message, title: title, icon: Icons.error_outline_rounded, backgroundColor: backgroundColor, action: action, enableHaptic: enableHaptic, ); } static void warning({ required BuildContext context, required String message, String title = "Warning", Color backgroundColor = const Color(0xFFD97706), SnackBarAction? action, bool enableHaptic = true, }) { show( context: context, message: message, title: title, icon: Icons.warning_amber_rounded, backgroundColor: backgroundColor, action: action, enableHaptic: enableHaptic, ); } static void info({ required BuildContext context, required String message, String title = "Info", Color backgroundColor = const Color(0xFF2563EB), SnackBarAction? action, bool enableHaptic = true, }) { show( context: context, message: message, title: title, icon: Icons.info_outline_rounded, backgroundColor: backgroundColor, action: action, enableHaptic: enableHaptic, ); } } class _AnimatedSnackBarContent extends StatefulWidget { final String message; final String? title; final IconData? icon; final Color backgroundColor; final Duration duration; final SnackBarAction? action; final bool showProgressBar; final Curve animationCurve; final VoidCallback onClose; const _AnimatedSnackBarContent({ required this.message, required this.title, required this.icon, required this.backgroundColor, required this.duration, required this.action, required this.showProgressBar, required this.animationCurve, required this.onClose, }); @override _AnimatedSnackBarContentState createState() => _AnimatedSnackBarContentState(); } class _AnimatedSnackBarContentState extends State<_AnimatedSnackBarContent> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _slideAnimation; late Animation _scaleAnimation; late Animation _fadeAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _slideAnimation = Tween( begin: const Offset(0, -2), end: Offset.zero, ).animate(CurvedAnimation( parent: _controller, curve: widget.animationCurve, )); _scaleAnimation = Tween( begin: 0.5, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: widget.animationCurve, )); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, )); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } void _close() { _controller.reverse().then((_) { widget.onClose(); }); } @override Widget build(BuildContext context) { return FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: ScaleTransition( scale: _scaleAnimation, child: Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: widget.backgroundColor, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: widget.backgroundColor.withOpacity(0.4), blurRadius: 25, offset: const Offset(0, 10), spreadRadius: 2, ), BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 8, offset: const Offset(0, 4), ), ], gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ widget.backgroundColor, Color.alphaBlend(Colors.white.withOpacity(0.15), widget.backgroundColor), ], ), border: Border.all( color: Colors.white.withOpacity(0.2), width: 1, ), ), child: Row( children: [ // Animated Icon Container Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: widget.backgroundColor.withOpacity(0.5), blurRadius: 8, spreadRadius: 1, ), ], ), child: Icon( widget.icon ?? Icons.notifications_none_rounded, color: Colors.white, size: 22, ), ), const SizedBox(width: 16), // Content Section Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (widget.title != null) ...[ Text( widget.title!, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 16, letterSpacing: -0.3, height: 1.2, ), ), const SizedBox(height: 4), ], Text( widget.message, style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 14, height: 1.4, fontWeight: FontWeight.w400, ), maxLines: 3, overflow: TextOverflow.ellipsis, ), // Action Button if (widget.action != null) ...[ const SizedBox(height: 8), Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.white.withOpacity(0.3), ), ), child: TextButton( onPressed: () { widget.action!.onPressed(); _close(); }, style: TextButton.styleFrom( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 4, ), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: Text( widget.action!.label, style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600, ), ), ), ), ], ], ), ), const SizedBox(width: 12), // Close Button GestureDetector( onTap: _close, child: Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( Icons.close_rounded, color: Colors.white.withOpacity(0.7), size: 18, ), ), ), ], ), ), ), ), ); } }