From 8e3af711e5943e0b1ce48dc0bf4401299b0d2398 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Wed, 28 Jan 2026 17:00:40 +0100 Subject: [PATCH] =?UTF-8?q?refactor(widgets):=20Extraire=20ChildCardWidget?= =?UTF-8?q?=20dans=20un=20fichier=20s=C3=A9par=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extraction du widget _ChildCardWidget de parent_register_step3_screen.dart vers un fichier réutilisable child_card_widget.dart Améliorations : - Widget désormais public (ChildCardWidget au lieu de _ChildCardWidget) - Réutilisable dans d'autres écrans (ex: récapitulatifs détaillés) - Imports nettoyés et simplifiés - Meilleure organisation du code Le widget gère : - Photo de l'enfant avec sélection d'image - Toggle "Enfant à naître" - Champs: Prénom, Nom, Date de naissance - Checkboxes: Consentement photo, Naissance multiple - Bouton de suppression (si > 1 enfant) --- .../auth/parent_register_step3_screen.dart | 214 +----------------- frontend/lib/widgets/child_card_widget.dart | 202 +++++++++++++++++ 2 files changed, 210 insertions(+), 206 deletions(-) create mode 100644 frontend/lib/widgets/child_card_widget.dart diff --git a/frontend/lib/screens/auth/parent_register_step3_screen.dart b/frontend/lib/screens/auth/parent_register_step3_screen.dart index f82636b..1f62801 100644 --- a/frontend/lib/screens/auth/parent_register_step3_screen.dart +++ b/frontend/lib/screens/auth/parent_register_step3_screen.dart @@ -1,22 +1,16 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron -import 'package:flutter/gestures.dart'; // Pour PointerDeviceKind -import '../../widgets/hover_relief_widget.dart'; // Import du nouveau widget import 'package:image_picker/image_picker.dart'; -// import 'package:image_cropper/image_cropper.dart'; // Supprimé -import 'dart:io' show File, Platform; // Ajout de Platform -import 'package:flutter/foundation.dart' show kIsWeb; // Import pour kIsWeb -import '../../widgets/custom_app_text_field.dart'; // Import du nouveau widget TextField -import '../../widgets/app_custom_checkbox.dart'; // Import du nouveau widget Checkbox -import '../../models/user_registration_data.dart'; // Import du modèle de données -import '../../utils/data_generator.dart'; // Import du générateur -import '../../models/card_assets.dart'; // Import des enums de cartes -import 'package:provider/provider.dart'; // Assurer l'import +import 'dart:io' show File; +import '../../widgets/hover_relief_widget.dart'; +import '../../widgets/child_card_widget.dart'; +import '../../models/user_registration_data.dart'; +import '../../utils/data_generator.dart'; +import '../../models/card_assets.dart'; +import 'package:provider/provider.dart'; import 'package:go_router/go_router.dart'; -// La classe _ChildFormData est supprimée car on utilise ChildData du modèle - class ParentRegisterStep3Screen extends StatefulWidget { // final UserRegistrationData registrationData; // Supprimé @@ -253,7 +247,7 @@ class _ParentRegisterStep3ScreenState extends State { // Carte Enfant return Padding( padding: const EdgeInsets.only(right: 20.0), - child: _ChildCardWidget( + child: ChildCardWidget( key: ValueKey(registrationData.children[index].hashCode), // Utiliser une clé basée sur les données childData: registrationData.children[index], childIndex: index, @@ -344,196 +338,4 @@ class _ParentRegisterStep3ScreenState extends State { ), ); } -} - -// Widget pour la carte enfant (adapté pour prendre ChildData et des callbacks) -class _ChildCardWidget extends StatefulWidget { // Transformé en StatefulWidget pour gérer les contrôleurs internes - final ChildData childData; - final int childIndex; - final VoidCallback onPickImage; - final VoidCallback onDateSelect; - final ValueChanged onFirstNameChanged; - final ValueChanged onLastNameChanged; - final ValueChanged onTogglePhotoConsent; - final ValueChanged onToggleMultipleBirth; - final ValueChanged onToggleIsUnborn; - final VoidCallback onRemove; - final bool canBeRemoved; - - const _ChildCardWidget({ - required Key key, - required this.childData, - required this.childIndex, - required this.onPickImage, - required this.onDateSelect, - required this.onFirstNameChanged, - required this.onLastNameChanged, - required this.onTogglePhotoConsent, - required this.onToggleMultipleBirth, - required this.onToggleIsUnborn, - required this.onRemove, - required this.canBeRemoved, - }) : super(key: key); - - @override - State<_ChildCardWidget> createState() => _ChildCardWidgetState(); -} - -class _ChildCardWidgetState extends State<_ChildCardWidget> { - late TextEditingController _firstNameController; - late TextEditingController _lastNameController; - late TextEditingController _dobController; - - @override - void initState() { - super.initState(); - // Initialiser les contrôleurs avec les données du widget - _firstNameController = TextEditingController(text: widget.childData.firstName); - _lastNameController = TextEditingController(text: widget.childData.lastName); - _dobController = TextEditingController(text: widget.childData.dob); - - // Ajouter des listeners pour mettre à jour les données sources via les callbacks - _firstNameController.addListener(() => widget.onFirstNameChanged(_firstNameController.text)); - _lastNameController.addListener(() => widget.onLastNameChanged(_lastNameController.text)); - // Pour dob, la mise à jour se fait via _selectDate, pas besoin de listener ici - } - - @override - void didUpdateWidget(covariant _ChildCardWidget oldWidget) { - super.didUpdateWidget(oldWidget); - // Mettre à jour les contrôleurs si les données externes changent - // (peut arriver si on recharge l'état global) - if (widget.childData.firstName != _firstNameController.text) { - _firstNameController.text = widget.childData.firstName; - } - if (widget.childData.lastName != _lastNameController.text) { - _lastNameController.text = widget.childData.lastName; - } - if (widget.childData.dob != _dobController.text) { - _dobController.text = widget.childData.dob; - } - } - - @override - void dispose() { - _firstNameController.dispose(); - _lastNameController.dispose(); - _dobController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final File? currentChildImage = widget.childData.imageFile; - // Utiliser la couleur de la carte de childData pour l'ombre si besoin, ou directement pour le fond - final Color baseCardColorForShadow = widget.childData.cardColor == CardColorVertical.lavender - ? Colors.purple.shade200 - : (widget.childData.cardColor == CardColorVertical.pink ? Colors.pink.shade200 : Colors.grey.shade200); // Placeholder pour autres couleurs - final Color initialPhotoShadow = baseCardColorForShadow.withAlpha(90); - final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130); - - return Container( - width: 345.0 * 1.1, // 379.5 - height: 570.0 * 1.2, // 684.0 - padding: const EdgeInsets.all(22.0 * 1.1), // 24.2 - decoration: BoxDecoration( - image: DecorationImage(image: AssetImage(widget.childData.cardColor.path), fit: BoxFit.cover), - borderRadius: BorderRadius.circular(20 * 1.1), // 22 - ), - child: Stack( - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - HoverReliefWidget( - onPressed: widget.onPickImage, - borderRadius: BorderRadius.circular(10), - initialShadowColor: initialPhotoShadow, - hoverShadowColor: hoverPhotoShadow, - child: SizedBox( - height: 200.0, - width: 200.0, - child: Center( - child: Padding( - padding: const EdgeInsets.all(5.0 * 1.1), // 5.5 - child: currentChildImage != null - ? ClipRRect(borderRadius: BorderRadius.circular(10 * 1.1), child: kIsWeb ? Image.network(currentChildImage.path, fit: BoxFit.cover) : Image.file(currentChildImage, fit: BoxFit.cover)) - : Image.asset('assets/images/photo.png', fit: BoxFit.contain), - ), - ), - ), - ), - const SizedBox(height: 12.0 * 1.1), // Augmenté pour plus d'espace après la photo - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Enfant à naître ?', style: GoogleFonts.merienda(fontSize: 16 * 1.1, fontWeight: FontWeight.w600)), - Switch(value: widget.childData.isUnbornChild, onChanged: widget.onToggleIsUnborn, activeColor: Theme.of(context).primaryColor), - ], - ), - const SizedBox(height: 9.0 * 1.1), // 9.9 - CustomAppTextField( - controller: _firstNameController, - labelText: 'Prénom', - hintText: 'Facultatif si à naître', - isRequired: !widget.childData.isUnbornChild, - fieldHeight: 55.0 * 1.1, // 60.5 - ), - const SizedBox(height: 6.0 * 1.1), // 6.6 - CustomAppTextField( - controller: _lastNameController, - labelText: 'Nom', - hintText: 'Nom de l\'enfant', - enabled: true, - fieldHeight: 55.0 * 1.1, // 60.5 - ), - const SizedBox(height: 9.0 * 1.1), // 9.9 - CustomAppTextField( - controller: _dobController, - labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance', - hintText: 'JJ/MM/AAAA', - readOnly: true, - onTap: widget.onDateSelect, - suffixIcon: Icons.calendar_today, - fieldHeight: 55.0 * 1.1, // 60.5 - ), - const SizedBox(height: 11.0 * 1.1), // 12.1 - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AppCustomCheckbox( - label: 'Consentement photo', - value: widget.childData.photoConsent, - onChanged: widget.onTogglePhotoConsent, - checkboxSize: 22.0 * 1.1, // 24.2 - ), - const SizedBox(height: 6.0 * 1.1), // 6.6 - AppCustomCheckbox( - label: 'Naissance multiple', - value: widget.childData.multipleBirth, - onChanged: widget.onToggleMultipleBirth, - checkboxSize: 22.0 * 1.1, // 24.2 - ), - ], - ), - ], - ), - if (widget.canBeRemoved) - Positioned( - top: -5, right: -5, - child: InkWell( - onTap: widget.onRemove, - customBorder: const CircleBorder(), - child: Image.asset( - 'images/red_cross2.png', - width: 36, - height: 36, - fit: BoxFit.contain, - ), - ), - ), - ], - ), - ); - } } \ No newline at end of file diff --git a/frontend/lib/widgets/child_card_widget.dart b/frontend/lib/widgets/child_card_widget.dart new file mode 100644 index 0000000..93f6044 --- /dev/null +++ b/frontend/lib/widgets/child_card_widget.dart @@ -0,0 +1,202 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'dart:io' show File; +import 'package:flutter/foundation.dart' show kIsWeb; +import '../models/user_registration_data.dart'; +import '../models/card_assets.dart'; +import 'custom_app_text_field.dart'; +import 'app_custom_checkbox.dart'; +import 'hover_relief_widget.dart'; + +/// Widget pour afficher et éditer une carte enfant +/// Utilisé dans le workflow d'inscription des parents +class ChildCardWidget extends StatefulWidget { + final ChildData childData; + final int childIndex; + final VoidCallback onPickImage; + final VoidCallback onDateSelect; + final ValueChanged onFirstNameChanged; + final ValueChanged onLastNameChanged; + final ValueChanged onTogglePhotoConsent; + final ValueChanged onToggleMultipleBirth; + final ValueChanged onToggleIsUnborn; + final VoidCallback onRemove; + final bool canBeRemoved; + + const ChildCardWidget({ + required Key key, + required this.childData, + required this.childIndex, + required this.onPickImage, + required this.onDateSelect, + required this.onFirstNameChanged, + required this.onLastNameChanged, + required this.onTogglePhotoConsent, + required this.onToggleMultipleBirth, + required this.onToggleIsUnborn, + required this.onRemove, + required this.canBeRemoved, + }) : super(key: key); + + @override + State createState() => _ChildCardWidgetState(); +} + +class _ChildCardWidgetState extends State { + late TextEditingController _firstNameController; + late TextEditingController _lastNameController; + late TextEditingController _dobController; + + @override + void initState() { + super.initState(); + // Initialiser les contrôleurs avec les données du widget + _firstNameController = TextEditingController(text: widget.childData.firstName); + _lastNameController = TextEditingController(text: widget.childData.lastName); + _dobController = TextEditingController(text: widget.childData.dob); + + // Ajouter des listeners pour mettre à jour les données sources via les callbacks + _firstNameController.addListener(() => widget.onFirstNameChanged(_firstNameController.text)); + _lastNameController.addListener(() => widget.onLastNameChanged(_lastNameController.text)); + // Pour dob, la mise à jour se fait via _selectDate, pas besoin de listener ici + } + + @override + void didUpdateWidget(covariant ChildCardWidget oldWidget) { + super.didUpdateWidget(oldWidget); + // Mettre à jour les contrôleurs si les données externes changent + // (peut arriver si on recharge l'état global) + if (widget.childData.firstName != _firstNameController.text) { + _firstNameController.text = widget.childData.firstName; + } + if (widget.childData.lastName != _lastNameController.text) { + _lastNameController.text = widget.childData.lastName; + } + if (widget.childData.dob != _dobController.text) { + _dobController.text = widget.childData.dob; + } + } + + @override + void dispose() { + _firstNameController.dispose(); + _lastNameController.dispose(); + _dobController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final File? currentChildImage = widget.childData.imageFile; + // Utiliser la couleur de la carte de childData pour l'ombre si besoin, ou directement pour le fond + final Color baseCardColorForShadow = widget.childData.cardColor == CardColorVertical.lavender + ? Colors.purple.shade200 + : (widget.childData.cardColor == CardColorVertical.pink ? Colors.pink.shade200 : Colors.grey.shade200); // Placeholder pour autres couleurs + final Color initialPhotoShadow = baseCardColorForShadow.withAlpha(90); + final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130); + + return Container( + width: 345.0 * 1.1, // 379.5 + height: 570.0 * 1.2, // 684.0 + padding: const EdgeInsets.all(22.0 * 1.1), // 24.2 + decoration: BoxDecoration( + image: DecorationImage(image: AssetImage(widget.childData.cardColor.path), fit: BoxFit.cover), + borderRadius: BorderRadius.circular(20 * 1.1), // 22 + ), + child: Stack( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + HoverReliefWidget( + onPressed: widget.onPickImage, + borderRadius: BorderRadius.circular(10), + initialShadowColor: initialPhotoShadow, + hoverShadowColor: hoverPhotoShadow, + child: SizedBox( + height: 200.0, + width: 200.0, + child: Center( + child: Padding( + padding: const EdgeInsets.all(5.0 * 1.1), // 5.5 + child: currentChildImage != null + ? ClipRRect(borderRadius: BorderRadius.circular(10 * 1.1), child: kIsWeb ? Image.network(currentChildImage.path, fit: BoxFit.cover) : Image.file(currentChildImage, fit: BoxFit.cover)) + : Image.asset('assets/images/photo.png', fit: BoxFit.contain), + ), + ), + ), + ), + const SizedBox(height: 12.0 * 1.1), // Augmenté pour plus d'espace après la photo + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Enfant à naître ?', style: GoogleFonts.merienda(fontSize: 16 * 1.1, fontWeight: FontWeight.w600)), + Switch(value: widget.childData.isUnbornChild, onChanged: widget.onToggleIsUnborn, activeColor: Theme.of(context).primaryColor), + ], + ), + const SizedBox(height: 9.0 * 1.1), // 9.9 + CustomAppTextField( + controller: _firstNameController, + labelText: 'Prénom', + hintText: 'Facultatif si à naître', + isRequired: !widget.childData.isUnbornChild, + fieldHeight: 55.0 * 1.1, // 60.5 + ), + const SizedBox(height: 6.0 * 1.1), // 6.6 + CustomAppTextField( + controller: _lastNameController, + labelText: 'Nom', + hintText: 'Nom de l\'enfant', + enabled: true, + fieldHeight: 55.0 * 1.1, // 60.5 + ), + const SizedBox(height: 9.0 * 1.1), // 9.9 + CustomAppTextField( + controller: _dobController, + labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance', + hintText: 'JJ/MM/AAAA', + readOnly: true, + onTap: widget.onDateSelect, + suffixIcon: Icons.calendar_today, + fieldHeight: 55.0 * 1.1, // 60.5 + ), + const SizedBox(height: 11.0 * 1.1), // 12.1 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppCustomCheckbox( + label: 'Consentement photo', + value: widget.childData.photoConsent, + onChanged: widget.onTogglePhotoConsent, + checkboxSize: 22.0 * 1.1, // 24.2 + ), + const SizedBox(height: 6.0 * 1.1), // 6.6 + AppCustomCheckbox( + label: 'Naissance multiple', + value: widget.childData.multipleBirth, + onChanged: widget.onToggleMultipleBirth, + checkboxSize: 22.0 * 1.1, // 24.2 + ), + ], + ), + ], + ), + if (widget.canBeRemoved) + Positioned( + top: -5, right: -5, + child: InkWell( + onTap: widget.onRemove, + customBorder: const CircleBorder(), + child: Image.asset( + 'assets/images/red_cross2.png', + width: 36, + height: 36, + fit: BoxFit.contain, + ), + ), + ), + ], + ), + ); + } +}