- 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>
221 lines
5.6 KiB
Markdown
221 lines
5.6 KiB
Markdown
# 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
|
|
|
|
```dart
|
|
// 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 :**
|
|
```dart
|
|
FormFieldWrapper(
|
|
config: config,
|
|
label: 'Prénom',
|
|
value: '',
|
|
controller: firstNameController,
|
|
onChanged: (value) => {},
|
|
hint: 'Entrez votre prénom',
|
|
)
|
|
```
|
|
|
|
**Mode readonly :**
|
|
```dart
|
|
FormFieldWrapper(
|
|
config: config,
|
|
label: 'Prénom',
|
|
value: 'Jean',
|
|
)
|
|
```
|
|
|
|
#### FormFieldRow
|
|
Widget pour afficher plusieurs champs sur une ligne (desktop) ou en colonne (mobile).
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
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
|
|
|
|
1. **Code unique** : Un seul widget pour éditable + readonly + mobile + desktop
|
|
2. **Cohérence** : Tous les formulaires se comportent de la même façon
|
|
3. **Maintenance** : Modification centralisée de l'UI
|
|
4. **Performance** : Pas de rebuild inutile, layout déterminé au build
|
|
5. **Simplicité** : API claire et prévisible
|
|
|
|
## 🔧 Utilitaires disponibles
|
|
|
|
```dart
|
|
// 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 :
|
|
|
|
1. Ajouter paramètre `DisplayMode mode`
|
|
2. Créer `DisplayConfig.fromContext(context, mode: widget.mode)`
|
|
3. Remplacer la structure Scaffold par `BaseFormScreen`
|
|
4. Remplacer les champs par `FormFieldWrapper`
|
|
5. Grouper les champs avec `FormFieldRow`
|
|
6. Tester en mode editable + readonly + mobile + desktop
|