import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../utils/email_utils.dart'; import 'custom_app_text_field.dart'; /// [TextFormField] e-mail : à la perte de focus → minuscules + validation immédiate. class EmailTextFormField extends StatefulWidget { const EmailTextFormField({ super.key, required this.controller, this.decoration, this.label, this.hint, this.allowEmpty = false, this.readOnly = false, this.focusNode, this.autovalidateMode, this.validator, }); final TextEditingController controller; final InputDecoration? decoration; final String? label; final String? hint; final bool allowEmpty; final bool readOnly; final FocusNode? focusNode; final AutovalidateMode? autovalidateMode; final String? Function(String?)? validator; @override State createState() => _EmailTextFormFieldState(); } class _EmailTextFormFieldState extends State { final GlobalKey> _fieldKey = GlobalKey>(); late final FocusNode _focusNode; late final bool _ownsFocusNode; @override void initState() { super.initState(); _ownsFocusNode = widget.focusNode == null; _focusNode = widget.focusNode ?? FocusNode(); _focusNode.addListener(_onFocusChange); } void _onFocusChange() { if (_focusNode.hasFocus || widget.readOnly) { return; } WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted || _focusNode.hasFocus) { return; } final c = widget.controller; final normalized = normalizeEmailText(c.text); if (normalized != c.text) { c.value = TextEditingValue( text: normalized, selection: TextSelection.collapsed(offset: normalized.length), ); } _fieldKey.currentState?.validate(); }); } @override void dispose() { _focusNode.removeListener(_onFocusChange); if (_ownsFocusNode) { _focusNode.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { final deco = widget.decoration ?? InputDecoration( labelText: widget.label, hintText: widget.hint, border: const OutlineInputBorder(), ); return TextFormField( key: _fieldKey, controller: widget.controller, focusNode: _focusNode, readOnly: widget.readOnly, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, autocorrect: false, enableSuggestions: false, autofillHints: const [AutofillHints.email], inputFormatters: const [EmailMaxLengthFormatter()], autovalidateMode: widget.autovalidateMode, decoration: deco, validator: widget.readOnly ? null : (widget.validator ?? (value) => validateEmail(value, allowEmpty: widget.allowEmpty)), ); } } /// Même logique que [EmailTextFormField] avec le style [CustomAppTextField]. class EmailCustomTextField extends StatefulWidget { const EmailCustomTextField({ super.key, required this.controller, required this.labelText, this.hintText = '', this.focusNode, this.fieldWidth = double.infinity, this.fieldHeight = 53.0, this.labelFontSize = 22.0, this.inputFontSize = 20.0, this.enabled = true, this.readOnly = false, this.allowEmpty = false, this.style = CustomAppTextFieldStyle.beige, this.validator, this.textInputAction, this.onFieldSubmitted, }); final TextEditingController controller; final String labelText; final String hintText; final FocusNode? focusNode; final double fieldWidth; final double fieldHeight; final double labelFontSize; final double inputFontSize; final bool enabled; final bool readOnly; final bool allowEmpty; final CustomAppTextFieldStyle style; final String? Function(String?)? validator; final TextInputAction? textInputAction; final ValueChanged? onFieldSubmitted; @override State createState() => _EmailCustomTextFieldState(); } class _EmailCustomTextFieldState extends State { final GlobalKey> _fieldKey = GlobalKey>(); late final FocusNode _focusNode; late final bool _ownsFocusNode; @override void initState() { super.initState(); _ownsFocusNode = widget.focusNode == null; _focusNode = widget.focusNode ?? FocusNode(); _focusNode.addListener(_onFocusChange); } void _onFocusChange() { if (_focusNode.hasFocus || widget.readOnly || !widget.enabled) { return; } WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted || _focusNode.hasFocus) { return; } final c = widget.controller; final normalized = normalizeEmailText(c.text); if (normalized != c.text) { c.value = TextEditingValue( text: normalized, selection: TextSelection.collapsed(offset: normalized.length), ); } _fieldKey.currentState?.validate(); }); } @override void dispose() { _focusNode.removeListener(_onFocusChange); if (_ownsFocusNode) { _focusNode.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return CustomAppTextField( formFieldKey: _fieldKey, controller: widget.controller, focusNode: _focusNode, labelText: widget.labelText, hintText: widget.hintText, style: widget.style, fieldWidth: widget.fieldWidth, fieldHeight: widget.fieldHeight, labelFontSize: widget.labelFontSize, inputFontSize: widget.inputFontSize, keyboardType: TextInputType.emailAddress, autocorrect: false, enableSuggestions: false, enabled: widget.enabled, readOnly: widget.readOnly, autofillHints: const [AutofillHints.email], textInputAction: widget.textInputAction ?? TextInputAction.next, onFieldSubmitted: widget.onFieldSubmitted, inputFormatters: const [EmailMaxLengthFormatter()], validator: widget.validator ?? ((v) => validateEmail(v, allowEmpty: widget.allowEmpty)), isRequired: false, ); } }