import 'package:flutter/material.dart'; import '../../theme/app_theme.dart'; /// Ensemble de boutons unifiés pour toute l'application /// /// Fournit des styles cohérents pour : /// - Boutons primaires, secondaires, tertiaires /// - Boutons d'action (success, warning, error) /// - Boutons avec icônes /// - États de chargement et désactivé class UnifiedButton extends StatefulWidget { /// Texte du bouton final String text; /// Icône optionnelle final IconData? icon; /// Position de l'icône final UnifiedButtonIconPosition iconPosition; /// Callback lors du tap final VoidCallback? onPressed; /// Style du bouton final UnifiedButtonStyle style; /// Taille du bouton final UnifiedButtonSize size; /// Indique si le bouton est en cours de chargement final bool isLoading; /// Indique si le bouton prend toute la largeur disponible final bool fullWidth; /// Couleur personnalisée final Color? customColor; const UnifiedButton({ super.key, required this.text, this.icon, this.iconPosition = UnifiedButtonIconPosition.left, this.onPressed, this.style = UnifiedButtonStyle.primary, this.size = UnifiedButtonSize.medium, this.isLoading = false, this.fullWidth = false, this.customColor, }); /// Constructeur pour bouton primaire const UnifiedButton.primary({ super.key, required this.text, this.icon, this.iconPosition = UnifiedButtonIconPosition.left, this.onPressed, this.size = UnifiedButtonSize.medium, this.isLoading = false, this.fullWidth = false, }) : style = UnifiedButtonStyle.primary, customColor = null; /// Constructeur pour bouton secondaire const UnifiedButton.secondary({ super.key, required this.text, this.icon, this.iconPosition = UnifiedButtonIconPosition.left, this.onPressed, this.size = UnifiedButtonSize.medium, this.isLoading = false, this.fullWidth = false, }) : style = UnifiedButtonStyle.secondary, customColor = null; /// Constructeur pour bouton tertiaire const UnifiedButton.tertiary({ super.key, required this.text, this.icon, this.iconPosition = UnifiedButtonIconPosition.left, this.onPressed, this.isLoading = false, this.size = UnifiedButtonSize.medium, this.fullWidth = false, }) : style = UnifiedButtonStyle.tertiary, customColor = null; /// Constructeur pour bouton de succès const UnifiedButton.success({ super.key, required this.text, this.icon, this.iconPosition = UnifiedButtonIconPosition.left, this.onPressed, this.size = UnifiedButtonSize.medium, this.isLoading = false, this.fullWidth = false, }) : style = UnifiedButtonStyle.success, customColor = null; /// Constructeur pour bouton d'erreur const UnifiedButton.error({ super.key, required this.text, this.icon, this.iconPosition = UnifiedButtonIconPosition.left, this.onPressed, this.size = UnifiedButtonSize.medium, this.isLoading = false, this.fullWidth = false, }) : style = UnifiedButtonStyle.error, customColor = null; @override State createState() => _UnifiedButtonState(); } class _UnifiedButtonState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _scaleAnimation; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 100), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 0.95, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final isEnabled = widget.onPressed != null && !widget.isLoading; return AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: SizedBox( width: widget.fullWidth ? double.infinity : null, height: _getButtonHeight(), child: GestureDetector( onTapDown: isEnabled ? (_) => _animationController.forward() : null, onTapUp: isEnabled ? (_) => _animationController.reverse() : null, onTapCancel: isEnabled ? () => _animationController.reverse() : null, child: ElevatedButton( onPressed: isEnabled ? widget.onPressed : null, style: _getButtonStyle(), child: widget.isLoading ? _buildLoadingContent() : _buildContent(), ), ), ), ); }, ); } double _getButtonHeight() { switch (widget.size) { case UnifiedButtonSize.small: return 36; case UnifiedButtonSize.medium: return 44; case UnifiedButtonSize.large: return 52; } } ButtonStyle _getButtonStyle() { final colors = _getColors(); return ElevatedButton.styleFrom( backgroundColor: colors.background, foregroundColor: colors.foreground, disabledBackgroundColor: colors.disabledBackground, disabledForegroundColor: colors.disabledForeground, elevation: _getElevation(), shadowColor: colors.shadow, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(_getBorderRadius()), side: _getBorderSide(colors), ), padding: _getPadding(), ); } _ButtonColors _getColors() { final customColor = widget.customColor; switch (widget.style) { case UnifiedButtonStyle.primary: return _ButtonColors( background: customColor ?? AppTheme.primaryColor, foreground: Colors.white, disabledBackground: AppTheme.surfaceVariant, disabledForeground: AppTheme.textSecondary, shadow: (customColor ?? AppTheme.primaryColor).withOpacity(0.3), ); case UnifiedButtonStyle.secondary: return _ButtonColors( background: Colors.white, foreground: customColor ?? AppTheme.primaryColor, disabledBackground: AppTheme.surfaceVariant, disabledForeground: AppTheme.textSecondary, shadow: Colors.black.withOpacity(0.1), borderColor: customColor ?? AppTheme.primaryColor, ); case UnifiedButtonStyle.tertiary: return _ButtonColors( background: Colors.transparent, foreground: customColor ?? AppTheme.primaryColor, disabledBackground: Colors.transparent, disabledForeground: AppTheme.textSecondary, shadow: Colors.transparent, ); case UnifiedButtonStyle.success: return _ButtonColors( background: customColor ?? AppTheme.successColor, foreground: Colors.white, disabledBackground: AppTheme.surfaceVariant, disabledForeground: AppTheme.textSecondary, shadow: (customColor ?? AppTheme.successColor).withOpacity(0.3), ); case UnifiedButtonStyle.warning: return _ButtonColors( background: customColor ?? AppTheme.warningColor, foreground: Colors.white, disabledBackground: AppTheme.surfaceVariant, disabledForeground: AppTheme.textSecondary, shadow: (customColor ?? AppTheme.warningColor).withOpacity(0.3), ); case UnifiedButtonStyle.error: return _ButtonColors( background: customColor ?? AppTheme.errorColor, foreground: Colors.white, disabledBackground: AppTheme.surfaceVariant, disabledForeground: AppTheme.textSecondary, shadow: (customColor ?? AppTheme.errorColor).withOpacity(0.3), ); } } double _getElevation() { switch (widget.style) { case UnifiedButtonStyle.primary: case UnifiedButtonStyle.success: case UnifiedButtonStyle.warning: case UnifiedButtonStyle.error: return 2; case UnifiedButtonStyle.secondary: return 1; case UnifiedButtonStyle.tertiary: return 0; } } double _getBorderRadius() { switch (widget.size) { case UnifiedButtonSize.small: return 8; case UnifiedButtonSize.medium: return 10; case UnifiedButtonSize.large: return 12; } } BorderSide _getBorderSide(_ButtonColors colors) { if (colors.borderColor != null) { return BorderSide(color: colors.borderColor!, width: 1); } return BorderSide.none; } EdgeInsetsGeometry _getPadding() { switch (widget.size) { case UnifiedButtonSize.small: return const EdgeInsets.symmetric(horizontal: 12, vertical: 6); case UnifiedButtonSize.medium: return const EdgeInsets.symmetric(horizontal: 16, vertical: 8); case UnifiedButtonSize.large: return const EdgeInsets.symmetric(horizontal: 20, vertical: 10); } } Widget _buildContent() { final List children = []; if (widget.icon != null && widget.iconPosition == UnifiedButtonIconPosition.left) { children.add(Icon(widget.icon, size: _getIconSize())); children.add(const SizedBox(width: 8)); } children.add( Text( widget.text, style: TextStyle( fontSize: _getFontSize(), fontWeight: FontWeight.w600, ), ), ); if (widget.icon != null && widget.iconPosition == UnifiedButtonIconPosition.right) { children.add(const SizedBox(width: 8)); children.add(Icon(widget.icon, size: _getIconSize())); } return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: children, ); } Widget _buildLoadingContent() { return SizedBox( width: _getIconSize(), height: _getIconSize(), child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( _getColors().foreground, ), ), ); } double _getIconSize() { switch (widget.size) { case UnifiedButtonSize.small: return 16; case UnifiedButtonSize.medium: return 18; case UnifiedButtonSize.large: return 20; } } double _getFontSize() { switch (widget.size) { case UnifiedButtonSize.small: return 12; case UnifiedButtonSize.medium: return 14; case UnifiedButtonSize.large: return 16; } } } /// Styles de boutons disponibles enum UnifiedButtonStyle { primary, secondary, tertiary, success, warning, error, } /// Tailles de boutons disponibles enum UnifiedButtonSize { small, medium, large, } /// Position de l'icône dans le bouton enum UnifiedButtonIconPosition { left, right, } /// Classe pour gérer les couleurs des boutons class _ButtonColors { final Color background; final Color foreground; final Color disabledBackground; final Color disabledForeground; final Color shadow; final Color? borderColor; const _ButtonColors({ required this.background, required this.foreground, required this.disabledBackground, required this.disabledForeground, required this.shadow, this.borderColor, }); }