feat(#156): création dossier AM staff — API + wizard dashboard.
Squash depuis develop : POST /assistantes-maternelles/dossier (actif + mail MDP), AmDossierWizard create/review, validations inscription. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,55 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:p_tits_pas/utils/email_utils.dart';
|
||||
import 'package:p_tits_pas/utils/nir_utils.dart';
|
||||
import 'package:p_tits_pas/utils/phone_utils.dart';
|
||||
import 'package:p_tits_pas/utils/postal_utils.dart';
|
||||
import 'admin_detail_modal.dart';
|
||||
|
||||
/// Réglages des formulaires validation / wizard AM — **jouer sur ces 3 leviers**.
|
||||
class ValidationFormMetrics {
|
||||
ValidationFormMetrics._();
|
||||
|
||||
// --- 1. Titres de section ---
|
||||
static const double sectionTitleFontSize = 16;
|
||||
static const double sectionTitleGapBelow = 12;
|
||||
|
||||
// --- 2. TF : texte intérieur + padding vertical (= hauteur) ---
|
||||
static const double fieldTextFontSize = 14;
|
||||
static const double fieldContentPaddingV = 12;
|
||||
static const double fieldContentPaddingH = 12;
|
||||
/// Hauteur estimée du TF (texte + padding haut/bas + bordure).
|
||||
static const double fieldHeight =
|
||||
fieldTextFontSize + fieldContentPaddingV * 2 + 4;
|
||||
|
||||
// --- 3. Espace entre les lignes de TF ---
|
||||
static const double rowGapBelow = 12;
|
||||
|
||||
// Libellé au-dessus du TF (titre du champ)
|
||||
static const double fieldLabelFontSize = 13;
|
||||
static const double fieldLabelGapBelow = 4;
|
||||
|
||||
static const TextStyle fieldTextStyle = TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: fieldTextFontSize,
|
||||
);
|
||||
|
||||
static double get sectionTitleBlockHeight =>
|
||||
sectionTitleFontSize + sectionTitleGapBelow;
|
||||
|
||||
static double get labeledRowHeight =>
|
||||
fieldLabelFontSize + fieldLabelGapBelow + fieldHeight + rowGapBelow;
|
||||
|
||||
/// Corps modale AM : padding wizard + titre + [rows] lignes + nav.
|
||||
static double shellBodyHeightForRows(int rows) =>
|
||||
20 * 2 + // padding wizard
|
||||
4 + // espace haut
|
||||
sectionTitleBlockHeight +
|
||||
rows * labeledRowHeight +
|
||||
24 + // avant nav
|
||||
44; // boutons
|
||||
}
|
||||
|
||||
/// Bloc type formulaire (titre de section + champs read-only) pour les modales de validation.
|
||||
/// [rowLayout] : même disposition que la création de compte, ex. [2, 2, 1, 2] = ligne de 2, ligne de 2, plein largeur, ligne de 2.
|
||||
/// [rowFlex] : flex par index de ligne (optionnel). Ex. {3: [2, 5]} = 4e ligne : code postal étroit (2), ville large (5).
|
||||
@@ -16,12 +64,16 @@ class ValidationDetailSection extends StatelessWidget {
|
||||
/// Flex par ligne (index de ligne -> [flex1, flex2, ...]). Ex. {3: [2, 5]} pour Code postal | Ville.
|
||||
final Map<int, List<int>>? rowFlex;
|
||||
|
||||
/// Remplit la hauteur disponible (wizard AM étapes 1–2).
|
||||
final bool expandVertically;
|
||||
|
||||
const ValidationDetailSection({
|
||||
super.key,
|
||||
this.title,
|
||||
required this.fields,
|
||||
this.rowLayout,
|
||||
this.rowFlex,
|
||||
this.expandVertically = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -30,6 +82,7 @@ class ValidationDetailSection extends StatelessWidget {
|
||||
title: title,
|
||||
rowLayout: rowLayout,
|
||||
rowFlex: rowFlex,
|
||||
expandVertically: expandVertically,
|
||||
fields: fields
|
||||
.map(
|
||||
(f) => ValidationLabeledField(
|
||||
@@ -49,6 +102,8 @@ class ValidationFormGrid extends StatelessWidget {
|
||||
final List<int>? rowLayout;
|
||||
final Map<int, List<int>>? rowFlex;
|
||||
final bool compact;
|
||||
/// Répartit la hauteur dispo entre les lignes (remplit le blanc sans scroll).
|
||||
final bool expandVertically;
|
||||
|
||||
const ValidationFormGrid({
|
||||
super.key,
|
||||
@@ -57,6 +112,7 @@ class ValidationFormGrid extends StatelessWidget {
|
||||
this.rowLayout,
|
||||
this.rowFlex,
|
||||
this.compact = false,
|
||||
this.expandVertically = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -64,7 +120,7 @@ class ValidationFormGrid extends StatelessWidget {
|
||||
final layout = rowLayout ?? List.filled(fields.length, 1);
|
||||
int index = 0;
|
||||
int rowIndex = 0;
|
||||
final rows = <Widget>[];
|
||||
final rowWidgets = <Widget>[];
|
||||
for (final count in layout) {
|
||||
if (index >= fields.length) break;
|
||||
final rowFields = fields.skip(index).take(count).toList();
|
||||
@@ -72,48 +128,73 @@ class ValidationFormGrid extends StatelessWidget {
|
||||
if (rowFields.isEmpty) continue;
|
||||
final flexForRow = rowFlex?[rowIndex];
|
||||
rowIndex++;
|
||||
final labeled = rowFields
|
||||
.map(
|
||||
(f) => ValidationLabeledField(
|
||||
label: f.label,
|
||||
field: f.field,
|
||||
expand: expandVertically,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
Widget row;
|
||||
if (count == 1) {
|
||||
rows.add(Padding(
|
||||
padding: EdgeInsets.only(bottom: compact ? 8 : 12),
|
||||
child: rowFields.first,
|
||||
));
|
||||
row = labeled.first;
|
||||
} else {
|
||||
rows.add(Padding(
|
||||
padding: EdgeInsets.only(bottom: compact ? 8 : 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (int i = 0; i < rowFields.length; i++) ...[
|
||||
if (i > 0) SizedBox(width: compact ? 12 : 16),
|
||||
Expanded(
|
||||
flex: (flexForRow != null && i < flexForRow.length)
|
||||
? flexForRow[i]
|
||||
: 1,
|
||||
child: rowFields[i],
|
||||
),
|
||||
],
|
||||
row = Row(
|
||||
crossAxisAlignment: expandVertically
|
||||
? CrossAxisAlignment.stretch
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (int i = 0; i < labeled.length; i++) ...[
|
||||
if (i > 0) SizedBox(width: compact ? 12 : 16),
|
||||
Expanded(
|
||||
flex: (flexForRow != null && i < flexForRow.length)
|
||||
? flexForRow[i]
|
||||
: 1,
|
||||
child: labeled[i],
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (expandVertically) {
|
||||
rowWidgets.add(Expanded(child: row));
|
||||
} else {
|
||||
rowWidgets.add(Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: compact ? 8 : ValidationFormMetrics.rowGapBelow,
|
||||
),
|
||||
child: row,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
final showTitle = title != null && title!.trim().isNotEmpty;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisSize: expandVertically ? MainAxisSize.max : MainAxisSize.min,
|
||||
children: [
|
||||
if (showTitle) ...[
|
||||
Text(
|
||||
title!.trim(),
|
||||
style: TextStyle(
|
||||
fontSize: compact ? 15 : 16,
|
||||
fontSize: compact
|
||||
? ValidationFormMetrics.sectionTitleFontSize - 1
|
||||
: ValidationFormMetrics.sectionTitleFontSize,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(height: compact ? 8 : 12),
|
||||
SizedBox(
|
||||
height: compact
|
||||
? 8
|
||||
: ValidationFormMetrics.sectionTitleGapBelow,
|
||||
),
|
||||
],
|
||||
...rows,
|
||||
...rowWidgets,
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -130,8 +211,8 @@ class ValidationFieldDecoration {
|
||||
fillColor: Colors.grey.shade50,
|
||||
hintText: hint,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: compact ? 10 : 12,
|
||||
vertical: compact ? 7 : 10,
|
||||
horizontal: compact ? 10 : ValidationFormMetrics.fieldContentPaddingH,
|
||||
vertical: compact ? 7 : ValidationFormMetrics.fieldContentPaddingV,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
@@ -180,29 +261,31 @@ class ValidationFieldDecoration {
|
||||
class ValidationLabeledField extends StatelessWidget {
|
||||
final String label;
|
||||
final Widget field;
|
||||
final bool expand;
|
||||
|
||||
const ValidationLabeledField({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.field,
|
||||
this.expand = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisSize: expand ? MainAxisSize.max : MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontSize: ValidationFormMetrics.fieldLabelFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
field,
|
||||
SizedBox(height: ValidationFormMetrics.fieldLabelGapBelow),
|
||||
if (expand) Expanded(child: field) else field,
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -264,13 +347,16 @@ class ValidationEditableField extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
if (!compact) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
keyboardType: keyboardType,
|
||||
inputFormatters: inputFormatters,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(color: Colors.black87, fontSize: 14),
|
||||
decoration: ValidationFieldDecoration.input(hint: hintText),
|
||||
return _validationFieldFillHeight(
|
||||
TextField(
|
||||
controller: controller,
|
||||
keyboardType: keyboardType,
|
||||
inputFormatters: inputFormatters,
|
||||
maxLines: 1,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
style: ValidationFormMetrics.fieldTextStyle,
|
||||
decoration: ValidationFieldDecoration.input(hint: hintText),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
@@ -295,6 +381,347 @@ class ValidationEditableField extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Étire le champ si le parent impose une hauteur (grille [expandVertically]).
|
||||
Widget _validationFieldFillHeight(Widget field) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, c) {
|
||||
if (!c.hasBoundedHeight || !c.maxHeight.isFinite) return field;
|
||||
return SizedBox(
|
||||
height: c.maxHeight,
|
||||
width: double.infinity,
|
||||
child: field,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// E-mail style validation — même supervision que login / création de compte :
|
||||
/// [EmailMaxLengthFormatter] + à la perte de focus : trim/minuscules + validation.
|
||||
class ValidationEmailField extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
final String? hintText;
|
||||
final bool allowEmpty;
|
||||
|
||||
const ValidationEmailField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.hintText,
|
||||
this.allowEmpty = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ValidationEmailField> createState() => _ValidationEmailFieldState();
|
||||
}
|
||||
|
||||
class _ValidationEmailFieldState extends State<ValidationEmailField> {
|
||||
final GlobalKey<FormFieldState<String>> _fieldKey =
|
||||
GlobalKey<FormFieldState<String>>();
|
||||
late final FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(_onFocusChange);
|
||||
}
|
||||
|
||||
void _onFocusChange() {
|
||||
if (_focusNode.hasFocus) 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);
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _validationFieldFillHeight(
|
||||
TextFormField(
|
||||
key: _fieldKey,
|
||||
controller: widget.controller,
|
||||
focusNode: _focusNode,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
textInputAction: TextInputAction.next,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
inputFormatters: const [EmailMaxLengthFormatter()],
|
||||
style: ValidationFormMetrics.fieldTextStyle,
|
||||
decoration:
|
||||
ValidationFieldDecoration.input(hint: widget.hintText).copyWith(
|
||||
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11),
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
validator: (value) =>
|
||||
validateEmail(value, allowEmpty: widget.allowEmpty),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Code postal FR — même supervision que création de compte :
|
||||
/// chiffres uniquement (max 5) + validation à la perte de focus.
|
||||
class ValidationPostalCodeField extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
final String? hintText;
|
||||
final bool allowEmpty;
|
||||
|
||||
const ValidationPostalCodeField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.hintText,
|
||||
this.allowEmpty = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ValidationPostalCodeField> createState() =>
|
||||
_ValidationPostalCodeFieldState();
|
||||
}
|
||||
|
||||
class _ValidationPostalCodeFieldState extends State<ValidationPostalCodeField> {
|
||||
final GlobalKey<FormFieldState<String>> _fieldKey =
|
||||
GlobalKey<FormFieldState<String>>();
|
||||
late final FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(_onFocusChange);
|
||||
}
|
||||
|
||||
void _onFocusChange() {
|
||||
if (_focusNode.hasFocus) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || _focusNode.hasFocus) return;
|
||||
final c = widget.controller;
|
||||
final trimmed = c.text.trim();
|
||||
if (trimmed != c.text) {
|
||||
c.value = TextEditingValue(
|
||||
text: trimmed,
|
||||
selection: TextSelection.collapsed(offset: trimmed.length),
|
||||
);
|
||||
}
|
||||
_fieldKey.currentState?.validate();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(_onFocusChange);
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _validationFieldFillHeight(
|
||||
TextFormField(
|
||||
key: _fieldKey,
|
||||
controller: widget.controller,
|
||||
focusNode: _focusNode,
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
inputFormatters: kFrenchPostalCodeInputFormatters,
|
||||
style: ValidationFormMetrics.fieldTextStyle,
|
||||
decoration: ValidationFieldDecoration.input(
|
||||
hint: widget.hintText ?? '5 chiffres',
|
||||
).copyWith(
|
||||
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11),
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
validator: (value) =>
|
||||
validateFrenchPostalCode(value, allowEmpty: widget.allowEmpty),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Téléphone FR — formatters live + [validateFrenchNationalPhone] à la perte de focus.
|
||||
class ValidationPhoneField extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
final String? hintText;
|
||||
final bool allowEmpty;
|
||||
|
||||
const ValidationPhoneField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.hintText,
|
||||
this.allowEmpty = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ValidationPhoneField> createState() => _ValidationPhoneFieldState();
|
||||
}
|
||||
|
||||
class _ValidationPhoneFieldState extends State<ValidationPhoneField> {
|
||||
final GlobalKey<FormFieldState<String>> _fieldKey =
|
||||
GlobalKey<FormFieldState<String>>();
|
||||
late final FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(_onFocusChange);
|
||||
}
|
||||
|
||||
void _onFocusChange() {
|
||||
if (_focusNode.hasFocus) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || _focusNode.hasFocus) return;
|
||||
final c = widget.controller;
|
||||
final digits = normalizePhone(c.text);
|
||||
final formatted = digits.isEmpty ? '' : formatPhoneForDisplay(digits);
|
||||
if (formatted != c.text) {
|
||||
c.value = TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: formatted.length),
|
||||
);
|
||||
}
|
||||
_fieldKey.currentState?.validate();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(_onFocusChange);
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _validationFieldFillHeight(
|
||||
TextFormField(
|
||||
key: _fieldKey,
|
||||
controller: widget.controller,
|
||||
focusNode: _focusNode,
|
||||
keyboardType: TextInputType.phone,
|
||||
textInputAction: TextInputAction.next,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
inputFormatters: frenchPhoneInputFormatters,
|
||||
style: ValidationFormMetrics.fieldTextStyle,
|
||||
decoration:
|
||||
ValidationFieldDecoration.input(hint: widget.hintText).copyWith(
|
||||
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11),
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
validator: (value) =>
|
||||
validateFrenchNationalPhone(value, allowEmpty: widget.allowEmpty),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// NIR — formatage live ([NirInputFormatter]) + validation au fil de la saisie / blur.
|
||||
class ValidationNirField extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
final String? hintText;
|
||||
final bool allowEmpty;
|
||||
|
||||
const ValidationNirField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.hintText,
|
||||
this.allowEmpty = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ValidationNirField> createState() => _ValidationNirFieldState();
|
||||
}
|
||||
|
||||
class _ValidationNirFieldState extends State<ValidationNirField> {
|
||||
final GlobalKey<FormFieldState<String>> _fieldKey =
|
||||
GlobalKey<FormFieldState<String>>();
|
||||
late final FocusNode _focusNode;
|
||||
bool _blurred = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(_onFocusChange);
|
||||
}
|
||||
|
||||
void _onFocusChange() {
|
||||
if (_focusNode.hasFocus) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || _focusNode.hasFocus) return;
|
||||
setState(() => _blurred = true);
|
||||
final c = widget.controller;
|
||||
final raw = nirToRaw(c.text).toUpperCase();
|
||||
final formatted = raw.isEmpty ? '' : formatNir(raw);
|
||||
if (formatted != c.text) {
|
||||
c.value = TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: formatted.length),
|
||||
);
|
||||
}
|
||||
_fieldKey.currentState?.validate();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(_onFocusChange);
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String? _validator(String? value) {
|
||||
if (_blurred) {
|
||||
if (widget.allowEmpty && (value == null || value.trim().isEmpty)) {
|
||||
return null;
|
||||
}
|
||||
return validateNir(value);
|
||||
}
|
||||
return validateNirTyping(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _validationFieldFillHeight(
|
||||
TextFormField(
|
||||
key: _fieldKey,
|
||||
controller: widget.controller,
|
||||
focusNode: _focusNode,
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.next,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
inputFormatters: const [NirInputFormatter()],
|
||||
style: ValidationFormMetrics.fieldTextStyle,
|
||||
decoration: ValidationFieldDecoration.input(
|
||||
hint: widget.hintText ?? '1 12 34 56 789 012 - 34',
|
||||
).copyWith(
|
||||
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11),
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
onChanged: (_) => _fieldKey.currentState?.validate(),
|
||||
validator: _validator,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Grille label/champ éditable (délègue à [ValidationFormGrid]).
|
||||
class ValidationEditableSection extends StatelessWidget {
|
||||
final List<ValidationLabeledField> fields;
|
||||
@@ -368,16 +795,19 @@ class _ValidationReadOnlyFieldState extends State<ValidationReadOnlyField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.compact && widget.maxLines == 1) {
|
||||
return TextField(
|
||||
controller: _controller,
|
||||
readOnly: true,
|
||||
enableInteractiveSelection: false,
|
||||
style: TextStyle(
|
||||
color: widget.error ? Colors.red.shade800 : Colors.black87,
|
||||
fontSize: 14,
|
||||
fontWeight: widget.error ? FontWeight.w600 : null,
|
||||
return _validationFieldFillHeight(
|
||||
TextField(
|
||||
controller: _controller,
|
||||
readOnly: true,
|
||||
enableInteractiveSelection: false,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
style: TextStyle(
|
||||
color: widget.error ? Colors.red.shade800 : Colors.black87,
|
||||
fontSize: ValidationFormMetrics.fieldTextFontSize,
|
||||
fontWeight: widget.error ? FontWeight.w600 : null,
|
||||
),
|
||||
decoration: ValidationFieldDecoration.readOnly(error: widget.error),
|
||||
),
|
||||
decoration: ValidationFieldDecoration.readOnly(error: widget.error),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user