import 'package:flutter/material.dart'; /// Bouton de soumission avec dégradé visuel et animations. /// /// Ce widget fournit un bouton de soumission moderne avec: /// - Dégradé de couleurs adaptatif au thème /// - Ombres et élévations /// - Support des états de chargement /// - Animations fluides /// /// **Usage:** /// ```dart /// SubmitButton( /// text: 'Créer l\'événement', /// onPressed: () { /// // Action de soumission /// }, /// isLoading: false, /// ) /// ``` class SubmitButton extends StatefulWidget { /// Crée un nouveau [SubmitButton]. /// /// [text] Le texte à afficher sur le bouton /// [onPressed] La fonction à exécuter lors du clic /// [isLoading] Si true, affiche un indicateur de chargement /// [isEnabled] Si false, désactive le bouton /// [icon] Une icône optionnelle à afficher avant le texte const SubmitButton({ required this.text, required this.onPressed, super.key, this.isLoading = false, this.isEnabled = true, this.icon, }); /// Le texte à afficher sur le bouton final String text; /// La fonction à exécuter lors du clic final VoidCallback onPressed; /// Si true, affiche un indicateur de chargement final bool isLoading; /// Si false, désactive le bouton final bool isEnabled; /// Une icône optionnelle à afficher avant le texte final IconData? icon; @override State createState() => _SubmitButtonState(); } class _SubmitButtonState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _scaleAnimation; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 150), ); _scaleAnimation = Tween(begin: 1.0, end: 0.95).animate( CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, ), ); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _handleTapDown(TapDownDetails details) { _animationController.forward(); } void _handleTapUp(TapUpDetails details) { _animationController.reverse(); } void _handleTapCancel() { _animationController.reverse(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDisabled = !widget.isEnabled || widget.isLoading; return ScaleTransition( scale: _scaleAnimation, child: GestureDetector( onTapDown: isDisabled ? null : _handleTapDown, onTapUp: isDisabled ? null : _handleTapUp, onTapCancel: isDisabled ? null : _handleTapCancel, child: Container( width: double.infinity, height: 56, decoration: BoxDecoration( gradient: LinearGradient( colors: isDisabled ? [ theme.colorScheme.surface, theme.colorScheme.surface, ] : [ theme.colorScheme.tertiary, theme.colorScheme.secondary, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: isDisabled ? null : [ BoxShadow( color: theme.colorScheme.tertiary.withOpacity(0.3), spreadRadius: 2, blurRadius: 8, offset: const Offset(0, 4), ), ], borderRadius: BorderRadius.circular(16), ), child: Material( color: Colors.transparent, child: InkWell( onTap: isDisabled ? null : widget.onPressed, borderRadius: BorderRadius.circular(16), child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: widget.isLoading ? Center( child: SizedBox( height: 24, width: 24, child: CircularProgressIndicator( strokeWidth: 2.5, valueColor: AlwaysStoppedAnimation( theme.colorScheme.onPrimary, ), ), ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ if (widget.icon != null) ...[ Icon( widget.icon, color: isDisabled ? theme.colorScheme.onSurface.withOpacity(0.38) : theme.colorScheme.onPrimary, size: 20, ), const SizedBox(width: 8), ], Text( widget.text, style: theme.textTheme.titleMedium?.copyWith( color: isDisabled ? theme.colorScheme.onSurface.withOpacity(0.38) : theme.colorScheme.onPrimary, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), textAlign: TextAlign.center, ), ], ), ), ), ), ), ), ); } }