- Support des modes Desktop/Mobile et Édition/Lecture seule - Refactoring des widgets de formulaire (PersonalInfo, ProfessionalInfo, Presentation, ChildCard) - Mise à jour des écrans de récapitulatif (ParentStep5, AmStep4) - Ajout de navigation (Précédent/Soumettre) sur mobile Closes #78 Co-authored-by: Cursor <cursoragent@cursor.com>
5.6 KiB
5.6 KiB
Infrastructure générique pour les formulaires
📋 Vue d'ensemble
Cette infrastructure permet de créer des formulaires qui s'adaptent automatiquement :
- Mode éditable (inscription) vs lecture seule (récapitulatif)
- Layout mobile (vertical, < 600px) vs desktop (horizontal, ≥ 600px)
- Mobile reste toujours vertical, même en rotation paysage
🏗️ Architecture
1. display_config.dart - Configuration centrale
// Mode d'affichage
enum DisplayMode {
editable, // Formulaire éditable
readonly, // Récapitulatif
}
// Type de layout
enum LayoutType {
mobile, // < 600px, toujours vertical
desktop, // ≥ 600px, horizontal
}
// Configuration complète
DisplayConfig config = DisplayConfig.fromContext(
context,
mode: DisplayMode.editable,
);
2. form_field_wrapper.dart - Champs génériques
FormFieldWrapper
Widget pour afficher un champ unique qui s'adapte automatiquement.
Mode éditable :
FormFieldWrapper(
config: config,
label: 'Prénom',
value: '',
controller: firstNameController,
onChanged: (value) => {},
hint: 'Entrez votre prénom',
)
Mode readonly :
FormFieldWrapper(
config: config,
label: 'Prénom',
value: 'Jean',
)
FormFieldRow
Widget pour afficher plusieurs champs sur une ligne (desktop) ou en colonne (mobile).
FormFieldRow(
config: config,
fields: [
FormFieldWrapper(...),
FormFieldWrapper(...),
],
)
3. base_form_screen.dart - Structure de page générique
Encapsule toute la structure d'une page de formulaire :
- En-tête (étape + titre)
- Carte avec fond adapté (horizontal/vertical)
- Boutons de navigation
- Gestion automatique du layout
BaseFormScreen(
config: DisplayConfig.fromContext(
context,
mode: DisplayMode.editable,
),
stepText: 'Étape 1/4',
title: 'Informations personnelles',
cardColor: CardColorHorizontal.blue,
previousRoute: '/previous',
onSubmit: () => _handleSubmit(),
content: Column(
children: [
FormFieldRow(
config: config,
fields: [
FormFieldWrapper(...),
FormFieldWrapper(...),
],
),
],
),
)
📱 Comportement responsive
Breakpoint : 600px
| Largeur écran | LayoutType | Orientation carte | Disposition champs |
|---|---|---|---|
| < 600px | mobile | Verticale | Colonne |
| ≥ 600px | desktop | Horizontale | Ligne |
Règle importante
Sur mobile, le layout reste TOUJOURS vertical, même si l'utilisateur tourne son téléphone en mode paysage.
🎨 Utilisation dans un widget de formulaire
Exemple : PersonalInfoFormScreen
class PersonalInfoFormScreen extends StatefulWidget {
final DisplayMode mode;
final PersonalInfoData? initialData;
final Function(PersonalInfoData) onSubmit;
// ...
}
class _PersonalInfoFormScreenState extends State<PersonalInfoFormScreen> {
late TextEditingController _firstNameController;
late TextEditingController _lastNameController;
@override
Widget build(BuildContext context) {
final config = DisplayConfig.fromContext(
context,
mode: widget.mode,
);
return BaseFormScreen(
config: config,
stepText: 'Étape 1/4',
title: 'Informations personnelles',
cardColor: CardColorHorizontal.blue,
previousRoute: '/previous',
onSubmit: _handleSubmit,
content: Column(
children: [
FormFieldRow(
config: config,
fields: [
FormFieldWrapper(
config: config,
label: 'Prénom',
value: _firstNameController.text,
controller: config.isEditable ? _firstNameController : null,
onChanged: config.isEditable ? (v) => setState(() {}) : null,
),
FormFieldWrapper(
config: config,
label: 'Nom',
value: _lastNameController.text,
controller: config.isEditable ? _lastNameController : null,
onChanged: config.isEditable ? (v) => setState(() {}) : null,
),
],
),
],
),
);
}
void _handleSubmit() {
final data = PersonalInfoData(
firstName: _firstNameController.text,
lastName: _lastNameController.text,
);
widget.onSubmit(data);
}
}
✅ Avantages
- Code unique : Un seul widget pour éditable + readonly + mobile + desktop
- Cohérence : Tous les formulaires se comportent de la même façon
- Maintenance : Modification centralisée de l'UI
- Performance : Pas de rebuild inutile, layout déterminé au build
- Simplicité : API claire et prévisible
🔧 Utilitaires disponibles
// Détecter le type de layout
bool isMobile = LayoutHelper.isMobile(context);
bool isDesktop = LayoutHelper.isDesktop(context);
// Espacement adaptatif
double spacing = LayoutHelper.getSpacing(
context,
mobileSpacing: 12.0,
desktopSpacing: 20.0,
);
// Largeur max adaptative
double maxWidth = LayoutHelper.getMaxWidth(context);
🚀 Migration des widgets existants
Pour migrer un widget existant vers cette infrastructure :
- Ajouter paramètre
DisplayMode mode - Créer
DisplayConfig.fromContext(context, mode: widget.mode) - Remplacer la structure Scaffold par
BaseFormScreen - Remplacer les champs par
FormFieldWrapper - Grouper les champs avec
FormFieldRow - Tester en mode editable + readonly + mobile + desktop