import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../theme/app_theme.dart'; class CustomTextField extends StatefulWidget { final TextEditingController controller; final String label; final String? hintText; final IconData? prefixIcon; final Widget? suffixIcon; final bool obscureText; final TextInputType keyboardType; final TextInputAction textInputAction; final String? Function(String?)? validator; final void Function(String)? onChanged; final void Function(String)? onFieldSubmitted; final bool enabled; final int maxLines; final int? maxLength; final List? inputFormatters; final bool autofocus; const CustomTextField({ super.key, required this.controller, required this.label, this.hintText, this.prefixIcon, this.suffixIcon, this.obscureText = false, this.keyboardType = TextInputType.text, this.textInputAction = TextInputAction.done, this.validator, this.onChanged, this.onFieldSubmitted, this.enabled = true, this.maxLines = 1, this.maxLength, this.inputFormatters, this.autofocus = false, }); @override State createState() => _CustomTextFieldState(); } class _CustomTextFieldState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _borderColorAnimation; late Animation _labelColorAnimation; bool _isFocused = false; String? _errorText; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); _borderColorAnimation = ColorTween( begin: AppTheme.borderColor, end: AppTheme.primaryColor, ).animate(_animationController); _labelColorAnimation = ColorTween( begin: AppTheme.textSecondary, end: AppTheme.primaryColor, ).animate(_animationController); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animationController, builder: (context, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Label Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( widget.label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: _labelColorAnimation.value, ), ), ), // Champ de saisie Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), boxShadow: _isFocused ? [ BoxShadow( color: AppTheme.primaryColor.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2), ), ] : null, ), child: TextFormField( controller: widget.controller, obscureText: widget.obscureText, keyboardType: widget.keyboardType, textInputAction: widget.textInputAction, enabled: widget.enabled, maxLines: widget.maxLines, maxLength: widget.maxLength, inputFormatters: widget.inputFormatters, autofocus: widget.autofocus, validator: (value) { final error = widget.validator?.call(value); setState(() { _errorText = error; }); return error; }, onChanged: widget.onChanged, onFieldSubmitted: widget.onFieldSubmitted, onTap: () { setState(() { _isFocused = true; }); _animationController.forward(); }, onTapOutside: (_) { setState(() { _isFocused = false; }); _animationController.reverse(); FocusScope.of(context).unfocus(); }, style: const TextStyle( fontSize: 16, color: AppTheme.textPrimary, ), decoration: InputDecoration( hintText: widget.hintText, hintStyle: const TextStyle( color: AppTheme.textHint, fontSize: 16, ), prefixIcon: widget.prefixIcon != null ? Icon( widget.prefixIcon, color: _isFocused ? AppTheme.primaryColor : AppTheme.textHint, ) : null, suffixIcon: widget.suffixIcon, filled: true, fillColor: widget.enabled ? Colors.white : AppTheme.backgroundLight, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: _borderColorAnimation.value ?? AppTheme.borderColor, width: _isFocused ? 2 : 1, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: _errorText != null ? AppTheme.errorColor : AppTheme.borderColor, width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: _errorText != null ? AppTheme.errorColor : AppTheme.primaryColor, width: 2, ), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide( color: AppTheme.errorColor, width: 1, ), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide( color: AppTheme.errorColor, width: 2, ), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), counterText: '', ), ), ), // Message d'erreur if (_errorText != null) Padding( padding: const EdgeInsets.only(top: 8, left: 4), child: Row( children: [ const Icon( Icons.error_outline, size: 16, color: AppTheme.errorColor, ), const SizedBox(width: 6), Expanded( child: Text( _errorText!, style: const TextStyle( color: AppTheme.errorColor, fontSize: 12, ), ), ), ], ), ), ], ); }, ); } }