import 'package:flutter/material.dart'; import '../../shared/theme/app_theme.dart'; /// Bouton animé avec effets visuels sophistiqués class AnimatedButton extends StatefulWidget { final String text; final IconData? icon; final VoidCallback? onPressed; final Color? backgroundColor; final Color? foregroundColor; final double? width; final double? height; final bool isLoading; final AnimatedButtonStyle style; const AnimatedButton({ super.key, required this.text, this.icon, this.onPressed, this.backgroundColor, this.foregroundColor, this.width, this.height, this.isLoading = false, this.style = AnimatedButtonStyle.primary, }); @override State createState() => _AnimatedButtonState(); } class _AnimatedButtonState extends State with TickerProviderStateMixin { late AnimationController _scaleController; late AnimationController _shimmerController; late AnimationController _loadingController; late Animation _scaleAnimation; late Animation _shimmerAnimation; late Animation _loadingAnimation; bool _isPressed = false; @override void initState() { super.initState(); _scaleController = AnimationController( duration: const Duration(milliseconds: 150), vsync: this, ); _shimmerController = AnimationController( duration: const Duration(milliseconds: 1500), vsync: this, ); _loadingController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 0.95, ).animate(CurvedAnimation( parent: _scaleController, curve: Curves.easeInOut, )); _shimmerAnimation = Tween( begin: -1.0, end: 1.0, ).animate(CurvedAnimation( parent: _shimmerController, curve: Curves.easeInOut, )); _loadingAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _loadingController, curve: Curves.easeInOut, )); if (widget.isLoading) { _loadingController.repeat(); } } @override void didUpdateWidget(AnimatedButton oldWidget) { super.didUpdateWidget(oldWidget); if (widget.isLoading != oldWidget.isLoading) { if (widget.isLoading) { _loadingController.repeat(); } else { _loadingController.stop(); _loadingController.reset(); } } } @override void dispose() { _scaleController.dispose(); _shimmerController.dispose(); _loadingController.dispose(); super.dispose(); } void _onTapDown(TapDownDetails details) { if (widget.onPressed != null && !widget.isLoading) { setState(() => _isPressed = true); _scaleController.forward(); } } void _onTapUp(TapUpDetails details) { if (widget.onPressed != null && !widget.isLoading) { setState(() => _isPressed = false); _scaleController.reverse(); _shimmerController.forward().then((_) { _shimmerController.reset(); }); } } void _onTapCancel() { if (widget.onPressed != null && !widget.isLoading) { setState(() => _isPressed = false); _scaleController.reverse(); } } @override Widget build(BuildContext context) { final colors = _getColors(); return AnimatedBuilder( animation: Listenable.merge([_scaleAnimation, _shimmerAnimation, _loadingAnimation]), builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: GestureDetector( onTapDown: _onTapDown, onTapUp: _onTapUp, onTapCancel: _onTapCancel, onTap: widget.onPressed != null && !widget.isLoading ? widget.onPressed : null, child: Container( width: widget.width, height: widget.height ?? 56, decoration: BoxDecoration( gradient: LinearGradient( colors: [ colors.backgroundColor, colors.backgroundColor.withOpacity(0.8), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: colors.backgroundColor.withOpacity(0.3), blurRadius: _isPressed ? 4 : 8, offset: Offset(0, _isPressed ? 2 : 4), ), ], ), child: Stack( children: [ // Effet shimmer if (!widget.isLoading) Positioned.fill( child: ClipRRect( borderRadius: BorderRadius.circular(16), child: AnimatedBuilder( animation: _shimmerAnimation, builder: (context, child) { return Transform.translate( offset: Offset(_shimmerAnimation.value * 200, 0), child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.transparent, Colors.white.withOpacity(0.2), Colors.transparent, ], stops: const [0.0, 0.5, 1.0], ), ), ), ); }, ), ), ), // Contenu du bouton Center( child: widget.isLoading ? _buildLoadingContent(colors) : _buildNormalContent(colors), ), ], ), ), ), ); }, ); } Widget _buildLoadingContent(_ButtonColors colors) { return Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(colors.foregroundColor), ), ), const SizedBox(width: 12), Text( 'Chargement...', style: TextStyle( color: colors.foregroundColor, fontSize: 16, fontWeight: FontWeight.w600, ), ), ], ); } Widget _buildNormalContent(_ButtonColors colors) { return Row( mainAxisSize: MainAxisSize.min, children: [ if (widget.icon != null) ...[ Icon( widget.icon, color: colors.foregroundColor, size: 20, ), const SizedBox(width: 8), ], Text( widget.text, style: TextStyle( color: colors.foregroundColor, fontSize: 16, fontWeight: FontWeight.w600, ), ), ], ); } _ButtonColors _getColors() { switch (widget.style) { case AnimatedButtonStyle.primary: return _ButtonColors( backgroundColor: widget.backgroundColor ?? AppTheme.primaryColor, foregroundColor: widget.foregroundColor ?? Colors.white, ); case AnimatedButtonStyle.secondary: return _ButtonColors( backgroundColor: widget.backgroundColor ?? AppTheme.secondaryColor, foregroundColor: widget.foregroundColor ?? Colors.white, ); case AnimatedButtonStyle.success: return _ButtonColors( backgroundColor: widget.backgroundColor ?? AppTheme.successColor, foregroundColor: widget.foregroundColor ?? Colors.white, ); case AnimatedButtonStyle.warning: return _ButtonColors( backgroundColor: widget.backgroundColor ?? AppTheme.warningColor, foregroundColor: widget.foregroundColor ?? Colors.white, ); case AnimatedButtonStyle.error: return _ButtonColors( backgroundColor: widget.backgroundColor ?? AppTheme.errorColor, foregroundColor: widget.foregroundColor ?? Colors.white, ); case AnimatedButtonStyle.outline: return _ButtonColors( backgroundColor: widget.backgroundColor ?? Colors.transparent, foregroundColor: widget.foregroundColor ?? AppTheme.primaryColor, ); } } } class _ButtonColors { final Color backgroundColor; final Color foregroundColor; _ButtonColors({ required this.backgroundColor, required this.foregroundColor, }); } enum AnimatedButtonStyle { primary, secondary, success, warning, error, outline, }