import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../../../core/models/evenement_model.dart'; import '../../../../shared/theme/app_theme.dart'; /// Carte d'événement avec animations sophistiquées class AnimatedEvenementCard extends StatefulWidget { final EvenementModel evenement; final VoidCallback? onTap; final VoidCallback? onFavorite; final bool showActions; const AnimatedEvenementCard({ super.key, required this.evenement, this.onTap, this.onFavorite, this.showActions = true, }); @override State createState() => _AnimatedEvenementCardState(); } class _AnimatedEvenementCardState extends State with TickerProviderStateMixin { late AnimationController _hoverController; late AnimationController _tapController; late AnimationController _favoriteController; late Animation _scaleAnimation; late Animation _elevationAnimation; late Animation _favoriteScaleAnimation; late Animation _favoriteColorAnimation; bool _isHovered = false; bool _isFavorite = false; @override void initState() { super.initState(); _hoverController = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); _tapController = AnimationController( duration: const Duration(milliseconds: 100), vsync: this, ); _favoriteController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 1.02, ).animate(CurvedAnimation( parent: _hoverController, curve: Curves.easeOutCubic, )); _elevationAnimation = Tween( begin: 2.0, end: 8.0, ).animate(CurvedAnimation( parent: _hoverController, curve: Curves.easeOutCubic, )); _favoriteScaleAnimation = Tween( begin: 1.0, end: 1.3, ).animate(CurvedAnimation( parent: _favoriteController, curve: Curves.elasticOut, )); _favoriteColorAnimation = ColorTween( begin: Colors.grey[400], end: Colors.red, ).animate(CurvedAnimation( parent: _favoriteController, curve: Curves.easeInOut, )); } @override void dispose() { _hoverController.dispose(); _tapController.dispose(); _favoriteController.dispose(); super.dispose(); } void _onTapDown(TapDownDetails details) { _tapController.forward(); } void _onTapUp(TapUpDetails details) { _tapController.reverse(); } void _onTapCancel() { _tapController.reverse(); } void _onHover(bool isHovered) { setState(() => _isHovered = isHovered); if (isHovered) { _hoverController.forward(); } else { _hoverController.reverse(); } } void _onFavoriteToggle() { setState(() => _isFavorite = !_isFavorite); if (_isFavorite) { _favoriteController.forward(); } else { _favoriteController.reverse(); } widget.onFavorite?.call(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final dateFormat = DateFormat('dd/MM/yyyy'); final timeFormat = DateFormat('HH:mm'); return AnimatedBuilder( animation: Listenable.merge([ _scaleAnimation, _elevationAnimation, _favoriteScaleAnimation, _favoriteColorAnimation, ]), builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: MouseRegion( onEnter: (_) => _onHover(true), onExit: (_) => _onHover(false), child: Card( elevation: _elevationAnimation.value, margin: EdgeInsets.zero, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), gradient: _isHovered ? LinearGradient( colors: [ Colors.white, AppTheme.primaryColor.withOpacity(0.02), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ) : null, ), child: InkWell( onTap: widget.onTap, onTapDown: _onTapDown, onTapUp: _onTapUp, onTapCancel: _onTapCancel, borderRadius: BorderRadius.circular(16), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // En-tête avec type et actions Row( children: [ // Icône du type avec animation AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _isHovered ? AppTheme.primaryColor.withOpacity(0.15) : AppTheme.primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( widget.evenement.typeEvenement.icone, style: const TextStyle(fontSize: 24), ), ), const SizedBox(width: 12), // Type et statut Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.evenement.typeEvenement.libelle, style: theme.textTheme.bodySmall?.copyWith( color: AppTheme.primaryColor, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 4), _buildStatusChip(), ], ), ), // Bouton favori animé if (widget.showActions) GestureDetector( onTap: _onFavoriteToggle, child: Transform.scale( scale: _favoriteScaleAnimation.value, child: Icon( _isFavorite ? Icons.favorite : Icons.favorite_border, color: _favoriteColorAnimation.value, size: 24, ), ), ), ], ), const SizedBox(height: 16), // Titre avec animation de couleur AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 200), style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, color: _isHovered ? AppTheme.primaryColor : theme.textTheme.titleLarge?.color, ) ?? const TextStyle(), child: Text(widget.evenement.titre), ), if (widget.evenement.description?.isNotEmpty == true) ...[ const SizedBox(height: 8), Text( widget.evenement.description!, style: theme.textTheme.bodyMedium?.copyWith( color: Colors.grey[600], ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], const SizedBox(height: 16), // Informations de date et lieu avec icônes animées Row( children: [ _buildAnimatedInfo( icon: Icons.calendar_today, text: dateFormat.format(widget.evenement.dateDebut), ), const SizedBox(width: 16), _buildAnimatedInfo( icon: Icons.access_time, text: timeFormat.format(widget.evenement.dateDebut), ), ], ), if (widget.evenement.lieu?.isNotEmpty == true) ...[ const SizedBox(height: 8), _buildAnimatedInfo( icon: Icons.location_on, text: widget.evenement.lieu!, ), ], ], ), ), ), ), ), ), ); }, ); } Widget _buildStatusChip() { Color statusColor; switch (widget.evenement.statut) { case StatutEvenement.planifie: statusColor = Colors.orange; break; case StatutEvenement.confirme: statusColor = Colors.green; break; case StatutEvenement.enCours: statusColor = Colors.blue; break; case StatutEvenement.termine: statusColor = Colors.grey; break; case StatutEvenement.annule: statusColor = Colors.red; break; case StatutEvenement.reporte: statusColor = Colors.purple; break; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: statusColor.withOpacity(0.3)), ), child: Text( widget.evenement.statut.libelle, style: TextStyle( color: statusColor, fontSize: 12, fontWeight: FontWeight.w600, ), ), ); } Widget _buildAnimatedInfo({required IconData icon, required String text}) { return Row( mainAxisSize: MainAxisSize.min, children: [ AnimatedContainer( duration: const Duration(milliseconds: 200), child: Icon( icon, size: 16, color: _isHovered ? AppTheme.primaryColor : Colors.grey[600], ), ), const SizedBox(width: 4), Text( text, style: TextStyle( color: Colors.grey[600], fontSize: 14, ), ), ], ); } }