petitspas/frontend/lib/widgets/form_field_wrapper.dart
Julien Martin 1d774f29eb feat(#78): Migrer PersonalInfoFormScreen vers infrastructure multi-modes
Migration du widget PersonalInfoFormScreen pour utiliser la nouvelle
infrastructure générique :

**Modifications PersonalInfoFormScreen:**
- Ajout paramètre DisplayMode mode (editable/readonly)
- Utilisation de DisplayConfig pour détecter mobile/desktop
- Utilisation de FormFieldRow pour layout responsive
- Adaptation automatique carte vertical/horizontal
- Boutons navigation adaptés mobile/desktop
- Conservation de toutes les fonctionnalités (toggles, validation, etc.)

**Corrections infrastructure:**
- base_form_screen.dart: Correction paramètres ImageButton (bg, textColor)
- form_field_wrapper.dart: Correction paramètres CustomAppTextField
- Gestion correcte des types nullables (TextInputType)

**Résultat:**
 Compilation sans erreurs
 Layout responsive fonctionnel
 Mode editable opérationnel
 Prêt pour mode readonly (récaps)

Référence: #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 17:38:54 +01:00

209 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../config/display_config.dart';
import 'custom_app_text_field.dart';
/// Widget générique pour afficher un champ de formulaire
/// S'adapte automatiquement selon le DisplayConfig (editable/readonly, mobile/desktop)
class FormFieldWrapper extends StatelessWidget {
/// Configuration d'affichage
final DisplayConfig config;
/// Label du champ
final String label;
/// Valeur actuelle
final String value;
/// Controller pour le mode éditable
final TextEditingController? controller;
/// Callback de changement (mode éditable)
final ValueChanged<String>? onChanged;
/// Hint du champ (mode éditable)
final String? hint;
/// Nombre de lignes (pour textarea)
final int? maxLines;
/// Type de clavier
final TextInputType? keyboardType;
/// Widget personnalisé à afficher (override le champ standard)
final Widget? customWidget;
const FormFieldWrapper({
super.key,
required this.config,
required this.label,
required this.value,
this.controller,
this.onChanged,
this.hint,
this.maxLines,
this.keyboardType,
this.customWidget,
});
@override
Widget build(BuildContext context) {
if (config.isReadonly) {
return _buildReadonlyField(context);
} else {
return _buildEditableField(context);
}
}
/// Construit un champ en mode lecture seule
Widget _buildReadonlyField(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: LayoutHelper.getSpacing(context,
mobileSpacing: 8.0,
desktopSpacing: 12.0,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Text(
label,
style: GoogleFonts.merienda(
fontSize: config.isMobile ? 14 : 16,
fontWeight: FontWeight.bold,
color: const Color(0xFF4A4A4A),
),
),
const SizedBox(height: 6),
// Valeur
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFFE0E0E0),
width: 1,
),
),
child: Text(
value.isEmpty ? '-' : value,
style: GoogleFonts.merienda(
fontSize: config.isMobile ? 14 : 16,
color: value.isEmpty
? const Color(0xFFBDBDBD)
: const Color(0xFF2C2C2C),
),
),
),
],
),
);
}
/// Construit un champ en mode éditable
Widget _buildEditableField(BuildContext context) {
// Si un widget personnalisé est fourni, l'utiliser
if (customWidget != null) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: LayoutHelper.getSpacing(context,
mobileSpacing: 8.0,
desktopSpacing: 12.0,
),
),
child: customWidget,
);
}
// Sinon, utiliser le champ standard
return Padding(
padding: EdgeInsets.symmetric(
vertical: LayoutHelper.getSpacing(context,
mobileSpacing: 8.0,
desktopSpacing: 12.0,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Text(
label,
style: GoogleFonts.merienda(
fontSize: config.isMobile ? 14 : 16,
fontWeight: FontWeight.w600,
color: const Color(0xFF4A4A4A),
),
),
const SizedBox(height: 8),
// Champ de saisie
CustomAppTextField(
controller: controller!,
labelText: label,
hintText: hint ?? label,
keyboardType: keyboardType ?? TextInputType.text,
fieldWidth: double.infinity,
),
],
),
);
}
}
/// Widget générique pour afficher une ligne de champs
/// S'adapte automatiquement: horizontal sur desktop, vertical sur mobile
class FormFieldRow extends StatelessWidget {
/// Configuration d'affichage
final DisplayConfig config;
/// Liste des champs à afficher
final List<Widget> fields;
/// Espacement entre les champs
final double? spacing;
const FormFieldRow({
super.key,
required this.config,
required this.fields,
this.spacing,
});
@override
Widget build(BuildContext context) {
final effectiveSpacing = spacing ??
LayoutHelper.getSpacing(context,
mobileSpacing: 12.0,
desktopSpacing: 20.0,
);
if (config.isMobile) {
// Layout vertical sur mobile
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: fields,
);
} else {
// Layout horizontal sur desktop
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (int i = 0; i < fields.length; i++) ...[
Expanded(child: fields[i]),
if (i < fields.length - 1) SizedBox(width: effectiveSpacing),
],
],
);
}
}
}