import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../theme/app_theme.dart'; enum ButtonVariant { primary, secondary, outline, ghost, gradient, glass, danger, success, } enum ButtonSize { small, medium, large, extraLarge, } enum ButtonShape { rounded, circular, square, } class SophisticatedButton extends StatefulWidget { final String? text; final Widget? child; final IconData? icon; final IconData? suffixIcon; final VoidCallback? onPressed; final VoidCallback? onLongPress; final ButtonVariant variant; final ButtonSize size; final ButtonShape shape; final Color? backgroundColor; final Color? foregroundColor; final Gradient? gradient; final bool loading; final bool disabled; final bool animated; final bool showRipple; final double? width; final double? height; final EdgeInsets? padding; final List? customShadow; final String? tooltip; final bool hapticFeedback; const SophisticatedButton({ super.key, this.text, this.child, this.icon, this.suffixIcon, this.onPressed, this.onLongPress, this.variant = ButtonVariant.primary, this.size = ButtonSize.medium, this.shape = ButtonShape.rounded, this.backgroundColor, this.foregroundColor, this.gradient, this.loading = false, this.disabled = false, this.animated = true, this.showRipple = true, this.width, this.height, this.padding, this.customShadow, this.tooltip, this.hapticFeedback = true, }); @override State createState() => _SophisticatedButtonState(); } class _SophisticatedButtonState extends State with TickerProviderStateMixin { late AnimationController _pressController; late AnimationController _loadingController; late AnimationController _shimmerController; late Animation _scaleAnimation; late Animation _shadowAnimation; late Animation _loadingAnimation; late Animation _shimmerAnimation; bool _isPressed = false; @override void initState() { super.initState(); _pressController = AnimationController( duration: const Duration(milliseconds: 150), vsync: this, ); _loadingController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); _shimmerController = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 0.95, ).animate(CurvedAnimation( parent: _pressController, curve: Curves.easeInOut, )); _shadowAnimation = Tween( begin: 1.0, end: 0.7, ).animate(CurvedAnimation( parent: _pressController, curve: Curves.easeInOut, )); _loadingAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _loadingController, curve: Curves.easeInOut, )); _shimmerAnimation = Tween( begin: -1.0, end: 2.0, ).animate(CurvedAnimation( parent: _shimmerController, curve: Curves.easeInOut, )); if (widget.loading) { _loadingController.repeat(); } // Shimmer effect for premium buttons if (widget.variant == ButtonVariant.gradient) { _shimmerController.repeat(); } } @override void didUpdateWidget(SophisticatedButton oldWidget) { super.didUpdateWidget(oldWidget); if (widget.loading != oldWidget.loading) { if (widget.loading) { _loadingController.repeat(); } else { _loadingController.reset(); } } } @override void dispose() { _pressController.dispose(); _loadingController.dispose(); _shimmerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final config = _getButtonConfig(); final isDisabled = widget.disabled || widget.loading; Widget button = AnimatedBuilder( animation: Listenable.merge([_pressController, _loadingController, _shimmerController]), builder: (context, child) { return Transform.scale( scale: widget.animated ? _scaleAnimation.value : 1.0, child: Container( width: widget.width, height: widget.height ?? _getHeight(), padding: widget.padding ?? _getPadding(), decoration: _getDecoration(config), child: Material( color: Colors.transparent, child: InkWell( onTap: isDisabled ? null : _handleTap, onLongPress: isDisabled ? null : widget.onLongPress, onTapDown: widget.animated && !isDisabled ? (_) => _pressController.forward() : null, onTapUp: widget.animated && !isDisabled ? (_) => _pressController.reverse() : null, onTapCancel: widget.animated && !isDisabled ? () => _pressController.reverse() : null, borderRadius: _getBorderRadius(), splashColor: widget.showRipple ? config.foregroundColor.withOpacity(0.2) : Colors.transparent, highlightColor: widget.showRipple ? config.foregroundColor.withOpacity(0.1) : Colors.transparent, child: _buildContent(config), ), ), ), ); }, ); if (widget.tooltip != null) { button = Tooltip( message: widget.tooltip!, child: button, ); } return button; } Widget _buildContent(_ButtonConfig config) { final hasIcon = widget.icon != null; final hasSuffixIcon = widget.suffixIcon != null; final hasText = widget.text != null || widget.child != null; if (widget.loading) { return _buildLoadingContent(config); } return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ if (hasIcon) ...[ _buildIcon(widget.icon!, config), if (hasText) SizedBox(width: _getIconSpacing()), ], if (hasText) ...[ Flexible(child: _buildText(config)), ], if (hasSuffixIcon) ...[ if (hasText || hasIcon) SizedBox(width: _getIconSpacing()), _buildIcon(widget.suffixIcon!, config), ], ], ); } Widget _buildLoadingContent(_ButtonConfig config) { return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: _getIconSize(), height: _getIconSize(), child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(config.foregroundColor), ), ), if (widget.text != null) ...[ SizedBox(width: _getIconSpacing()), Text( 'Chargement...', style: _getTextStyle(config), ), ], ], ); } Widget _buildIcon(IconData icon, _ButtonConfig config) { return Icon( icon, size: _getIconSize(), color: config.foregroundColor, ); } Widget _buildText(_ButtonConfig config) { if (widget.child != null) { return DefaultTextStyle( style: _getTextStyle(config), child: widget.child!, ); } return Text( widget.text!, style: _getTextStyle(config), textAlign: TextAlign.center, ); } _ButtonConfig _getButtonConfig() { final isDisabled = widget.disabled || widget.loading; switch (widget.variant) { case ButtonVariant.primary: return _ButtonConfig( backgroundColor: isDisabled ? AppTheme.textHint : (widget.backgroundColor ?? AppTheme.primaryColor), foregroundColor: isDisabled ? AppTheme.textSecondary : (widget.foregroundColor ?? Colors.white), hasElevation: true, ); case ButtonVariant.secondary: return _ButtonConfig( backgroundColor: isDisabled ? AppTheme.backgroundLight : (widget.backgroundColor ?? AppTheme.secondaryColor), foregroundColor: isDisabled ? AppTheme.textHint : (widget.foregroundColor ?? Colors.white), hasElevation: true, ); case ButtonVariant.outline: return _ButtonConfig( backgroundColor: Colors.transparent, foregroundColor: isDisabled ? AppTheme.textHint : (widget.foregroundColor ?? AppTheme.primaryColor), borderColor: isDisabled ? AppTheme.textHint : (widget.backgroundColor ?? AppTheme.primaryColor), hasElevation: false, ); case ButtonVariant.ghost: return _ButtonConfig( backgroundColor: isDisabled ? Colors.transparent : (widget.backgroundColor ?? AppTheme.primaryColor).withOpacity(0.1), foregroundColor: isDisabled ? AppTheme.textHint : (widget.foregroundColor ?? AppTheme.primaryColor), hasElevation: false, ); case ButtonVariant.gradient: return _ButtonConfig( backgroundColor: Colors.transparent, foregroundColor: isDisabled ? AppTheme.textHint : (widget.foregroundColor ?? Colors.white), hasElevation: true, useGradient: true, ); case ButtonVariant.glass: return _ButtonConfig( backgroundColor: isDisabled ? Colors.grey.withOpacity(0.1) : Colors.white.withOpacity(0.2), foregroundColor: isDisabled ? AppTheme.textHint : (widget.foregroundColor ?? AppTheme.textPrimary), borderColor: Colors.white.withOpacity(0.3), hasElevation: true, isGlass: true, ); case ButtonVariant.danger: return _ButtonConfig( backgroundColor: isDisabled ? AppTheme.textHint : AppTheme.errorColor, foregroundColor: isDisabled ? AppTheme.textSecondary : Colors.white, hasElevation: true, ); case ButtonVariant.success: return _ButtonConfig( backgroundColor: isDisabled ? AppTheme.textHint : AppTheme.successColor, foregroundColor: isDisabled ? AppTheme.textSecondary : Colors.white, hasElevation: true, ); } } Decoration _getDecoration(_ButtonConfig config) { final borderRadius = _getBorderRadius(); final isDisabled = widget.disabled || widget.loading; if (config.useGradient && !isDisabled) { return BoxDecoration( gradient: widget.gradient ?? LinearGradient( colors: [ widget.backgroundColor ?? AppTheme.primaryColor, (widget.backgroundColor ?? AppTheme.primaryColor).withOpacity(0.7), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: borderRadius, boxShadow: config.hasElevation ? _getShadow(config) : null, ); } return BoxDecoration( color: config.backgroundColor, borderRadius: borderRadius, border: config.borderColor != null ? Border.all(color: config.borderColor!, width: 1.5) : null, boxShadow: config.hasElevation && !isDisabled ? _getShadow(config) : null, ); } List _getShadow(_ButtonConfig config) { if (widget.customShadow != null) { return widget.customShadow!.map((shadow) => BoxShadow( color: shadow.color.withOpacity(shadow.color.opacity * _shadowAnimation.value), blurRadius: shadow.blurRadius * _shadowAnimation.value, offset: shadow.offset * _shadowAnimation.value, spreadRadius: shadow.spreadRadius, )).toList(); } final shadowColor = config.useGradient ? (widget.backgroundColor ?? AppTheme.primaryColor) : config.backgroundColor; return [ BoxShadow( color: shadowColor.withOpacity(0.3 * _shadowAnimation.value), blurRadius: 15 * _shadowAnimation.value, offset: Offset(0, 8 * _shadowAnimation.value), ), ]; } BorderRadius _getBorderRadius() { switch (widget.shape) { case ButtonShape.rounded: return BorderRadius.circular(_getHeight() / 2); case ButtonShape.circular: return BorderRadius.circular(_getHeight()); case ButtonShape.square: return BorderRadius.circular(8); } } double _getHeight() { switch (widget.size) { case ButtonSize.small: return 32; case ButtonSize.medium: return 44; case ButtonSize.large: return 56; case ButtonSize.extraLarge: return 72; } } EdgeInsets _getPadding() { switch (widget.size) { case ButtonSize.small: return const EdgeInsets.symmetric(horizontal: 16, vertical: 6); case ButtonSize.medium: return const EdgeInsets.symmetric(horizontal: 24, vertical: 12); case ButtonSize.large: return const EdgeInsets.symmetric(horizontal: 32, vertical: 16); case ButtonSize.extraLarge: return const EdgeInsets.symmetric(horizontal: 40, vertical: 20); } } double _getFontSize() { switch (widget.size) { case ButtonSize.small: return 14; case ButtonSize.medium: return 16; case ButtonSize.large: return 18; case ButtonSize.extraLarge: return 20; } } double _getIconSize() { switch (widget.size) { case ButtonSize.small: return 16; case ButtonSize.medium: return 20; case ButtonSize.large: return 24; case ButtonSize.extraLarge: return 28; } } double _getIconSpacing() { switch (widget.size) { case ButtonSize.small: return 6; case ButtonSize.medium: return 8; case ButtonSize.large: return 10; case ButtonSize.extraLarge: return 12; } } TextStyle _getTextStyle(_ButtonConfig config) { return TextStyle( fontSize: _getFontSize(), fontWeight: FontWeight.w600, color: config.foregroundColor, letterSpacing: 0.5, ); } void _handleTap() { if (widget.hapticFeedback) { HapticFeedback.lightImpact(); } widget.onPressed?.call(); } } class _ButtonConfig { final Color backgroundColor; final Color foregroundColor; final Color? borderColor; final bool hasElevation; final bool useGradient; final bool isGlass; _ButtonConfig({ required this.backgroundColor, required this.foregroundColor, this.borderColor, this.hasElevation = false, this.useGradient = false, this.isGlass = false, }); }