import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; // Définition de l'enum pour les styles de couleur/fond enum CustomAppTextFieldStyle { beige, lavande, jaune, } class CustomAppTextField extends StatefulWidget { final TextEditingController controller; final FocusNode? focusNode; final String labelText; final String hintText; final double fieldWidth; final double fieldHeight; final bool obscureText; final TextInputType keyboardType; final String? Function(String?)? validator; final CustomAppTextFieldStyle style; final bool isRequired; final bool enabled; final bool readOnly; final VoidCallback? onTap; final IconData? suffixIcon; final double labelFontSize; final double inputFontSize; /// Espace vertical entre le libellé et la zone de saisie. final double labelFieldSpacing; final bool showLabel; final Iterable? autofillHints; final TextInputAction? textInputAction; final ValueChanged? onFieldSubmitted; /// Souvent appelé par la touche « suivant » du clavier (flèche), là où [onFieldSubmitted] ne l’est pas. final VoidCallback? onEditingComplete; final List? inputFormatters; final bool autocorrect; final bool enableSuggestions; final GlobalKey>? formFieldKey; const CustomAppTextField({ super.key, required this.controller, this.focusNode, required this.labelText, this.showLabel = true, this.hintText = '', this.fieldWidth = 300.0, this.fieldHeight = 53.0, this.obscureText = false, this.keyboardType = TextInputType.text, this.validator, this.style = CustomAppTextFieldStyle.beige, this.isRequired = false, this.enabled = true, this.readOnly = false, this.onTap, this.suffixIcon, this.labelFontSize = 18.0, this.inputFontSize = 18.0, this.labelFieldSpacing = 6.0, this.autofillHints, this.textInputAction, this.onFieldSubmitted, this.onEditingComplete, this.inputFormatters, this.autocorrect = true, this.enableSuggestions = true, this.formFieldKey, }); @override State createState() => _CustomAppTextFieldState(); } class _CustomAppTextFieldState extends State { String getBackgroundImagePath() { switch (widget.style) { case CustomAppTextFieldStyle.lavande: return 'assets/images/bg_lavender.png'; case CustomAppTextFieldStyle.jaune: return 'assets/images/bg_yellow.png'; case CustomAppTextFieldStyle.beige: default: return 'assets/images/bg_beige.png'; } } @override Widget build(BuildContext context) { final double dynamicFieldHeight = widget.fieldHeight; // Indication « non éditable » : libellé + hint en gris. final Color labelColor = widget.enabled ? Colors.black87 : Colors.grey; final Color hintColor = widget.enabled ? Colors.black54.withOpacity(0.7) : Colors.grey; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (widget.showLabel) ...[ Text( widget.labelText, style: GoogleFonts.merienda( fontSize: widget.labelFontSize, color: labelColor, fontWeight: FontWeight.w500, ), ), SizedBox(height: widget.labelFieldSpacing), ], // Pas de hauteur fixe sur le TextFormField : le message d’erreur du // validateur s’affiche en dessous ; un SizedBox fixe le masquait. SizedBox( width: widget.fieldWidth, child: Stack( clipBehavior: Clip.none, alignment: Alignment.centerLeft, children: [ Positioned.fill( child: Image.asset( getBackgroundImagePath(), fit: BoxFit.fill, ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 8.0), child: ConstrainedBox( constraints: BoxConstraints( minHeight: (dynamicFieldHeight - 16.0).clamp(24.0, double.infinity), ), child: TextFormField( key: widget.formFieldKey, controller: widget.controller, focusNode: widget.focusNode, obscureText: widget.obscureText, keyboardType: widget.keyboardType, autocorrect: widget.autocorrect, enableSuggestions: widget.enableSuggestions, inputFormatters: widget.inputFormatters, autofillHints: widget.autofillHints, textInputAction: widget.textInputAction, onFieldSubmitted: widget.onFieldSubmitted, onEditingComplete: widget.onEditingComplete, enabled: widget.enabled, readOnly: widget.readOnly, onTap: widget.onTap, style: GoogleFonts.merienda( fontSize: widget.inputFontSize, color: Colors.black87), validator: widget.validator ?? (value) { if (!widget.enabled || widget.readOnly) return null; if (widget.isRequired && (value == null || value.isEmpty)) { return 'Ce champ est obligatoire'; } return null; }, decoration: InputDecoration( hintText: widget.hintText, hintStyle: GoogleFonts.merienda( fontSize: widget.inputFontSize, color: hintColor), border: InputBorder.none, contentPadding: EdgeInsets.zero, isDense: true, errorStyle: GoogleFonts.merienda( fontSize: widget.inputFontSize * 0.75, color: Colors.red.shade800, height: 1.2, ), errorMaxLines: 3, suffixIcon: widget.suffixIcon != null ? Padding( padding: const EdgeInsets.only(right: 0.0), child: Icon(widget.suffixIcon, color: Colors.black54, size: widget.inputFontSize * 1.1), ) : null, ), textAlignVertical: TextAlignVertical.center, ), ), ), ], ), ), ], ); } }