import 'package:flutter/material.dart'; import '../../theme/app_theme.dart'; /// Widget de carte unifié pour toute l'application /// /// Fournit un design cohérent avec : /// - Styles standardisés (élévation, bordures, couleurs) /// - Support des animations hover et tap /// - Variantes de style (elevated, outlined, filled) /// - Gestion des états (loading, disabled) class UnifiedCard extends StatefulWidget { /// Contenu principal de la carte final Widget child; /// Callback lors du tap sur la carte final VoidCallback? onTap; /// Callback lors du long press final VoidCallback? onLongPress; /// Padding interne de la carte final EdgeInsetsGeometry? padding; /// Marge externe de la carte final EdgeInsetsGeometry? margin; /// Largeur de la carte final double? width; /// Hauteur de la carte final double? height; /// Variante de style de la carte final UnifiedCardVariant variant; /// Couleur de fond personnalisée final Color? backgroundColor; /// Couleur de bordure personnalisée final Color? borderColor; /// Indique si la carte est désactivée final bool disabled; /// Indique si la carte est en cours de chargement final bool loading; /// Élévation personnalisée final double? elevation; /// Rayon des bordures personnalisé final double? borderRadius; const UnifiedCard({ super.key, required this.child, this.onTap, this.onLongPress, this.padding, this.margin, this.width, this.height, this.variant = UnifiedCardVariant.elevated, this.backgroundColor, this.borderColor, this.disabled = false, this.loading = false, this.elevation, this.borderRadius, }); /// Constructeur pour une carte élevée const UnifiedCard.elevated({ super.key, required this.child, this.onTap, this.onLongPress, this.padding, this.margin, this.width, this.height, this.backgroundColor, this.disabled = false, this.loading = false, this.elevation, this.borderRadius, }) : variant = UnifiedCardVariant.elevated, borderColor = null; /// Constructeur pour une carte avec bordure const UnifiedCard.outlined({ super.key, required this.child, this.onTap, this.onLongPress, this.padding, this.margin, this.width, this.height, this.backgroundColor, this.borderColor, this.disabled = false, this.loading = false, this.elevation, this.borderRadius, }) : variant = UnifiedCardVariant.outlined; /// Constructeur pour une carte remplie const UnifiedCard.filled({ super.key, required this.child, this.onTap, this.onLongPress, this.padding, this.margin, this.width, this.height, this.backgroundColor, this.borderColor, this.disabled = false, this.loading = false, this.elevation, this.borderRadius, }) : variant = UnifiedCardVariant.filled; /// Constructeur pour une carte KPI const UnifiedCard.kpi({ super.key, required this.child, this.onTap, this.onLongPress, this.margin, this.width, this.height, this.backgroundColor, this.disabled = false, this.loading = false, }) : variant = UnifiedCardVariant.elevated, padding = const EdgeInsets.all(20), borderColor = null, elevation = 2, borderRadius = 16; /// Constructeur pour une carte de liste const UnifiedCard.listItem({ super.key, required this.child, this.onTap, this.onLongPress, this.margin, this.width, this.height, this.backgroundColor, this.disabled = false, this.loading = false, }) : variant = UnifiedCardVariant.outlined, padding = const EdgeInsets.all(16), borderColor = null, elevation = 0, borderRadius = 12; @override State createState() => _UnifiedCardState(); } class _UnifiedCardState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _scaleAnimation; late Animation _elevationAnimation; bool _isHovered = false; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 150), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 0.98, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); _elevationAnimation = Tween( begin: _getBaseElevation(), end: _getBaseElevation() + 2, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); } @override void dispose() { _animationController.dispose(); super.dispose(); } double _getBaseElevation() { if (widget.elevation != null) return widget.elevation!; switch (widget.variant) { case UnifiedCardVariant.elevated: return 2; case UnifiedCardVariant.outlined: return 0; case UnifiedCardVariant.filled: return 1; } } Color _getBackgroundColor() { if (widget.backgroundColor != null) return widget.backgroundColor!; if (widget.disabled) return AppTheme.surfaceVariant.withOpacity(0.5); switch (widget.variant) { case UnifiedCardVariant.elevated: return Colors.white; case UnifiedCardVariant.outlined: return Colors.white; case UnifiedCardVariant.filled: return AppTheme.surfaceVariant; } } Border? _getBorder() { if (widget.variant == UnifiedCardVariant.outlined) { return Border.all( color: widget.borderColor ?? AppTheme.outline, width: 1, ); } return null; } @override Widget build(BuildContext context) { Widget card = AnimatedBuilder( animation: _animationController, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( width: widget.width, height: widget.height, margin: widget.margin, decoration: BoxDecoration( color: _getBackgroundColor(), borderRadius: BorderRadius.circular(widget.borderRadius ?? 12), border: _getBorder(), boxShadow: widget.variant == UnifiedCardVariant.elevated ? [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: _elevationAnimation.value * 2, offset: Offset(0, _elevationAnimation.value), ), ] : null, ), child: ClipRRect( borderRadius: BorderRadius.circular(widget.borderRadius ?? 12), child: Material( color: Colors.transparent, child: widget.loading ? _buildLoadingState() : Padding( padding: widget.padding ?? const EdgeInsets.all(16), child: widget.child, ), ), ), ), ); }, ); if (widget.onTap != null && !widget.disabled && !widget.loading) { card = MouseRegion( onEnter: (_) => _onHover(true), onExit: (_) => _onHover(false), child: GestureDetector( onTap: widget.onTap, onLongPress: widget.onLongPress, onTapDown: (_) => _animationController.forward(), onTapUp: (_) => _animationController.reverse(), onTapCancel: () => _animationController.reverse(), child: card, ), ); } return card; } void _onHover(bool isHovered) { if (mounted && !widget.disabled && !widget.loading) { setState(() { _isHovered = isHovered; }); if (isHovered) { _animationController.forward(); } else { _animationController.reverse(); } } } Widget _buildLoadingState() { return Container( padding: widget.padding ?? const EdgeInsets.all(16), child: const Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(AppTheme.primaryColor), ), ), ), ); } } /// Variantes de style pour les cartes unifiées enum UnifiedCardVariant { /// Carte avec élévation et ombre elevated, /// Carte avec bordure uniquement outlined, /// Carte avec fond coloré filled, }