- Backend: mail après inscription parent avec n° dossier, UPLOAD_PHOTOS_DIR, réponse API - Frontend: imageBytes + payload photo, utils email/code postal/téléphone, champs dédiés - Formulaires admin, login (focus), personal_info, child_card, custom_app_text_field Made-with: Cursor
220 lines
6.2 KiB
Dart
220 lines
6.2 KiB
Dart
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<EmailTextFormField> createState() => _EmailTextFormFieldState();
|
|
}
|
|
|
|
class _EmailTextFormFieldState extends State<EmailTextFormField> {
|
|
final GlobalKey<FormFieldState<String>> _fieldKey =
|
|
GlobalKey<FormFieldState<String>>();
|
|
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 <TextInputFormatter>[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<String>? onFieldSubmitted;
|
|
|
|
@override
|
|
State<EmailCustomTextField> createState() => _EmailCustomTextFieldState();
|
|
}
|
|
|
|
class _EmailCustomTextFieldState extends State<EmailCustomTextField> {
|
|
final GlobalKey<FormFieldState<String>> _fieldKey =
|
|
GlobalKey<FormFieldState<String>>();
|
|
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,
|
|
);
|
|
}
|
|
}
|