import 'package:flutter/material.dart'; import '../../shared/theme/app_theme.dart'; /// Service de notifications animées class AnimatedNotifications { static OverlayEntry? _currentOverlay; /// Affiche une notification de succès static void showSuccess( BuildContext context, String message, { Duration duration = const Duration(seconds: 3), }) { _showNotification( context, message, NotificationType.success, duration, ); } /// Affiche une notification d'erreur static void showError( BuildContext context, String message, { Duration duration = const Duration(seconds: 4), }) { _showNotification( context, message, NotificationType.error, duration, ); } /// Affiche une notification d'information static void showInfo( BuildContext context, String message, { Duration duration = const Duration(seconds: 3), }) { _showNotification( context, message, NotificationType.info, duration, ); } /// Affiche une notification d'avertissement static void showWarning( BuildContext context, String message, { Duration duration = const Duration(seconds: 3), }) { _showNotification( context, message, NotificationType.warning, duration, ); } static void _showNotification( BuildContext context, String message, NotificationType type, Duration duration, ) { // Supprimer la notification précédente si elle existe _currentOverlay?.remove(); final overlay = Overlay.of(context); late OverlayEntry overlayEntry; overlayEntry = OverlayEntry( builder: (context) => AnimatedNotificationWidget( message: message, type: type, onDismiss: () { overlayEntry.remove(); _currentOverlay = null; }, ), ); _currentOverlay = overlayEntry; overlay.insert(overlayEntry); // Auto-dismiss après la durée spécifiée Future.delayed(duration, () { if (_currentOverlay == overlayEntry) { overlayEntry.remove(); _currentOverlay = null; } }); } /// Masque la notification actuelle static void dismiss() { _currentOverlay?.remove(); _currentOverlay = null; } } /// Widget de notification animée class AnimatedNotificationWidget extends StatefulWidget { final String message; final NotificationType type; final VoidCallback onDismiss; const AnimatedNotificationWidget({ super.key, required this.message, required this.type, required this.onDismiss, }); @override State createState() => _AnimatedNotificationWidgetState(); } class _AnimatedNotificationWidgetState extends State with TickerProviderStateMixin { late AnimationController _slideController; late AnimationController _fadeController; late AnimationController _scaleController; late Animation _slideAnimation; late Animation _fadeAnimation; late Animation _scaleAnimation; @override void initState() { super.initState(); _slideController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _fadeController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _scaleController = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); _slideAnimation = Tween( begin: const Offset(0, -1), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: Curves.elasticOut, )); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _fadeController, curve: Curves.easeOut, )); _scaleAnimation = Tween( begin: 1.0, end: 1.05, ).animate(CurvedAnimation( parent: _scaleController, curve: Curves.easeInOut, )); // Démarrer les animations d'entrée _fadeController.forward(); _slideController.forward(); } @override void dispose() { _slideController.dispose(); _fadeController.dispose(); _scaleController.dispose(); super.dispose(); } void _dismiss() async { await _fadeController.reverse(); widget.onDismiss(); } @override Widget build(BuildContext context) { final colors = _getColors(); return Positioned( top: MediaQuery.of(context).padding.top + 16, left: 16, right: 16, child: AnimatedBuilder( animation: Listenable.merge([ _slideAnimation, _fadeAnimation, _scaleAnimation, ]), builder: (context, child) { return SlideTransition( position: _slideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Transform.scale( scale: _scaleAnimation.value, child: GestureDetector( onTap: () => _scaleController.forward().then((_) { _scaleController.reverse(); }), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ colors.backgroundColor, colors.backgroundColor.withOpacity(0.9), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: colors.backgroundColor.withOpacity(0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Row( children: [ // Icône Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: colors.iconBackgroundColor, borderRadius: BorderRadius.circular(12), ), child: Icon( colors.icon, color: colors.iconColor, size: 24, ), ), const SizedBox(width: 12), // Message Expanded( child: Text( widget.message, style: TextStyle( color: colors.textColor, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), // Bouton de fermeture GestureDetector( onTap: _dismiss, child: Container( padding: const EdgeInsets.all(4), child: Icon( Icons.close, color: colors.textColor.withOpacity(0.7), size: 20, ), ), ), ], ), ), ), ), ), ); }, ), ); } _NotificationColors _getColors() { switch (widget.type) { case NotificationType.success: return _NotificationColors( backgroundColor: AppTheme.successColor, textColor: Colors.white, icon: Icons.check_circle, iconColor: Colors.white, iconBackgroundColor: Colors.white.withOpacity(0.2), ); case NotificationType.error: return _NotificationColors( backgroundColor: AppTheme.errorColor, textColor: Colors.white, icon: Icons.error, iconColor: Colors.white, iconBackgroundColor: Colors.white.withOpacity(0.2), ); case NotificationType.warning: return _NotificationColors( backgroundColor: AppTheme.warningColor, textColor: Colors.white, icon: Icons.warning, iconColor: Colors.white, iconBackgroundColor: Colors.white.withOpacity(0.2), ); case NotificationType.info: return _NotificationColors( backgroundColor: AppTheme.primaryColor, textColor: Colors.white, icon: Icons.info, iconColor: Colors.white, iconBackgroundColor: Colors.white.withOpacity(0.2), ); } } } class _NotificationColors { final Color backgroundColor; final Color textColor; final IconData icon; final Color iconColor; final Color iconBackgroundColor; _NotificationColors({ required this.backgroundColor, required this.textColor, required this.icon, required this.iconColor, required this.iconBackgroundColor, }); } enum NotificationType { success, error, warning, info, }