refactor(inscription): Refonte complète du processus d'inscription - Modèles etdonnées: Suppression de placeholder_registration_data.dart, ajout de user_registration_data.dart, data_generator.dart et card_assets.dart - Interface utilisateur: Refonte des écrans d'inscription, amélioration des widgets, ajout de cartes colorées - Assets: Ajout de nouvelles cartes colorées - Configuration: Mise à jour de pubspec.yaml et app_router.dart

This commit is contained in:
Julien Martin
2025-05-12 12:00:49 +02:00
parent acb602643a
commit 1496f7f174
32 changed files with 1035 additions and 717 deletions
@@ -1,6 +1,7 @@
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é
@@ -8,92 +9,69 @@ 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
// Classe de données pour un enfant
class _ChildFormData {
final Key key; // Pour aider Flutter à identifier les widgets dans une liste
final TextEditingController firstNameController;
final TextEditingController lastNameController;
final TextEditingController dobController;
bool photoConsent;
bool multipleBirth;
bool isUnbornChild;
File? imageFile;
_ChildFormData({
required this.key,
String initialFirstName = '',
String initialLastName = '',
String initialDob = '',
this.photoConsent = false,
this.multipleBirth = false,
this.isUnbornChild = false,
this.imageFile,
}) : firstNameController = TextEditingController(text: initialFirstName),
lastNameController = TextEditingController(text: initialLastName),
dobController = TextEditingController(text: initialDob);
// Méthode pour disposer les contrôleurs
void dispose() {
firstNameController.dispose();
lastNameController.dispose();
dobController.dispose();
}
}
// La classe _ChildFormData est supprimée car on utilise ChildData du modèle
class ParentRegisterStep3Screen extends StatefulWidget {
const ParentRegisterStep3Screen({super.key});
final UserRegistrationData registrationData; // Accepte les données
const ParentRegisterStep3Screen({super.key, required this.registrationData});
@override
State<ParentRegisterStep3Screen> createState() => _ParentRegisterStep3ScreenState();
}
class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
// TODO: Gérer une liste d'enfants et leurs contrôleurs respectifs
// List<ChildData> _children = [ChildData()]; // Commencer avec un enfant
final _formKey = GlobalKey<FormState>(); // Une clé par enfant sera nécessaire si validation complexe
// Liste pour stocker les données de chaque enfant
List<_ChildFormData> _childrenDataList = [];
final ScrollController _scrollController = ScrollController(); // Ajout du ScrollController
late UserRegistrationData _registrationData; // Stocke l'état complet
final ScrollController _scrollController = ScrollController(); // Pour le défilement horizontal
bool _isScrollable = false;
bool _showLeftFade = false;
bool _showRightFade = false;
static const double _fadeExtent = 0.05; // Pourcentage de la vue pour le fondu (5%)
static const double _fadeExtent = 0.05; // Pourcentage de fondu
// Liste ordonnée des couleurs de cartes pour les enfants
static const List<CardColorVertical> _childCardColors = [
CardColorVertical.lavender, // Premier enfant toujours lavande
CardColorVertical.pink,
CardColorVertical.peach,
CardColorVertical.lime,
CardColorVertical.red,
CardColorVertical.green,
CardColorVertical.blue,
];
// Utilisation de GlobalKey pour les cartes enfants si validation complexe future
// Map<int, GlobalKey<FormState>> _childFormKeys = {};
@override
void initState() {
super.initState();
_addChild();
_registrationData = widget.registrationData;
// S'il n'y a pas d'enfant, en ajouter un automatiquement avec des données générées
if (_registrationData.children.isEmpty) {
_addChild();
}
_scrollController.addListener(_scrollListener);
// Appel initial pour définir l'état des fondus après le premier layout
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollListener());
}
@override
void dispose() {
// Disposer les contrôleurs de tous les enfants
for (var childData in _childrenDataList) {
childData.dispose();
}
_scrollController.removeListener(_scrollListener); // Ne pas oublier de retirer le listener
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
super.dispose();
}
void _scrollListener() {
if (!_scrollController.hasClients) return; // S'assurer que le controller est attaché
if (!_scrollController.hasClients) return;
final position = _scrollController.position;
final newIsScrollable = position.maxScrollExtent > 0.0;
// Le fondu à gauche est affiché si on a scrollé plus loin que la moitié de la zone de fondu
final newShowLeftFade = newIsScrollable && position.pixels > (position.viewportDimension * _fadeExtent / 2);
// Le fondu à droite est affiché s'il reste à scroller plus que la moitié de la zone de fondu
final newShowRightFade = newIsScrollable && position.pixels < (position.maxScrollExtent - (position.viewportDimension * _fadeExtent / 2));
if (newIsScrollable != _isScrollable ||
newShowLeftFade != _showLeftFade ||
newShowRightFade != _showRightFade) {
if (newIsScrollable != _isScrollable || newShowLeftFade != _showLeftFade || newShowRightFade != _showRightFade) {
setState(() {
_isScrollable = newIsScrollable;
_showLeftFade = newShowLeftFade;
@@ -103,110 +81,99 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
}
void _addChild() {
String initialLastName = '';
if (_childrenDataList.isNotEmpty) {
initialLastName = _childrenDataList.first.lastNameController.text;
}
setState(() {
_childrenDataList.add(_ChildFormData(
key: UniqueKey(),
initialLastName: initialLastName,
));
bool isUnborn = DataGenerator.boolean();
// Déterminer la couleur de la carte pour le nouvel enfant
final cardColor = _childCardColors[_registrationData.children.length % _childCardColors.length];
final newChild = ChildData(
lastName: _registrationData.parent1.lastName, // Hérite du nom de famille du parent 1
firstName: DataGenerator.firstName(),
dob: DataGenerator.dob(isUnborn: isUnborn),
isUnbornChild: isUnborn,
photoConsent: DataGenerator.boolean(),
multipleBirth: DataGenerator.boolean(),
cardColor: cardColor, // Assigner la couleur
// imageFile: null, // Pas d'image générée pour l'instant
);
_registrationData.addChild(newChild);
// Ajouter une clé de formulaire si nécessaire
// _childFormKeys[_registrationData.children.length - 1] = GlobalKey<FormState>();
});
// S'assurer que le listener est appelé après la mise à jour de l'UI
// et faire défiler vers la fin si possible
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollListener(); // Mettre à jour l'état des fondus
_scrollListener();
if (_scrollController.hasClients && _scrollController.position.maxScrollExtent > 0.0) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
_scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
}
});
}
// Méthode pour sélectionner une image (devra être adaptée pour l'index)
void _removeChild(int index) {
if (_registrationData.children.length > 1 && index >= 0 && index < _registrationData.children.length) {
setState(() {
_registrationData.children.removeAt(index);
// Supprimer aussi la clé de formulaire associée si utilisée
// _childFormKeys.remove(index);
// Il faudrait aussi décaler les clés des enfants suivants si on utilise les index comme clés de map
});
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollListener());
}
}
Future<void> _pickImage(int childIndex) async {
final ImagePicker picker = ImagePicker();
try {
final XFile? pickedFile = await picker.pickImage(
source: ImageSource.gallery,
imageQuality: 70,
maxWidth: 1024,
maxHeight: 1024,
);
source: ImageSource.gallery, imageQuality: 70, maxWidth: 1024, maxHeight: 1024);
if (pickedFile != null) {
setState(() {
if (childIndex < _childrenDataList.length) {
_childrenDataList[childIndex].imageFile = File(pickedFile.path);
if (childIndex < _registrationData.children.length) {
_registrationData.children[childIndex].imageFile = File(pickedFile.path);
}
});
} // Fin de if (pickedFile != null)
} catch (e) {
print("Erreur lors de la sélection de l'image: $e");
}
}
} catch (e) { print("Erreur image: $e"); }
}
Future<void> _selectDate(BuildContext context, int childIndex) async {
final _ChildFormData currentChild = _childrenDataList[childIndex];
final ChildData currentChild = _registrationData.children[childIndex];
final DateTime now = DateTime.now();
DateTime initialDatePickerDate = now;
DateTime firstDatePickerDate = DateTime(1980);
DateTime lastDatePickerDate = now;
DateTime firstDatePickerDate = DateTime(1980); DateTime lastDatePickerDate = now;
if (currentChild.isUnbornChild) {
firstDatePickerDate = now;
lastDatePickerDate = now.add(const Duration(days: 300));
if (currentChild.dobController.text.isNotEmpty) {
firstDatePickerDate = now; lastDatePickerDate = now.add(const Duration(days: 300));
if (currentChild.dob.isNotEmpty) {
try {
List<String> parts = currentChild.dobController.text.split('/');
List<String> parts = currentChild.dob.split('/');
DateTime? parsedDate = DateTime.tryParse("${parts[2]}-${parts[1].padLeft(2, '0')}-${parts[0].padLeft(2, '0')}");
if (parsedDate != null && !parsedDate.isBefore(firstDatePickerDate) && !parsedDate.isAfter(lastDatePickerDate)) {
initialDatePickerDate = parsedDate;
}
} catch (e) { /* Ignorer */ }
} catch (e) {}
}
} else {
if (currentChild.dobController.text.isNotEmpty) {
if (currentChild.dob.isNotEmpty) {
try {
List<String> parts = currentChild.dobController.text.split('/');
List<String> parts = currentChild.dob.split('/');
DateTime? parsedDate = DateTime.tryParse("${parts[2]}-${parts[1].padLeft(2, '0')}-${parts[0].padLeft(2, '0')}");
if (parsedDate != null && !parsedDate.isBefore(firstDatePickerDate) && !parsedDate.isAfter(lastDatePickerDate)) {
initialDatePickerDate = parsedDate;
}
} catch (e) { /* Ignorer */ }
} catch (e) {}
}
}
final DateTime? picked = await showDatePicker(
context: context,
initialDate: initialDatePickerDate,
firstDate: firstDatePickerDate,
lastDate: lastDatePickerDate,
locale: const Locale('fr', 'FR'),
context: context, initialDate: initialDatePickerDate, firstDate: firstDatePickerDate,
lastDate: lastDatePickerDate, locale: const Locale('fr', 'FR'),
);
if (picked != null) {
setState(() {
currentChild.dobController.text = "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}";
currentChild.dob = "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}";
});
}
}
void _removeChild(Key key) {
setState(() {
// Trouver et supprimer l'enfant par sa clé, et s'assurer qu'il en reste au moins un.
if (_childrenDataList.length > 1) {
_childrenDataList.removeWhere((child) => child.key == key);
}
});
// S'assurer que le listener est appelé après la mise à jour de l'UI
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollListener());
}
@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
@@ -217,101 +184,79 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat),
),
Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Étape 3/X', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
const SizedBox(height: 10),
Text(
'Merci de renseigner les informations de/vos enfant(s) :',
style: GoogleFonts.merienda(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87),
textAlign: TextAlign.center,
),
const SizedBox(height: 30),
Padding( // Ajout du Padding pour les marges latérales
padding: const EdgeInsets.symmetric(horizontal: 150.0), // Marge de 150px de chaque côté
child: SizedBox(
height: 500,
child: ShaderMask(
shaderCallback: (Rect bounds) {
// Déterminer les couleurs du gradient en fonction de l'état de défilement
final Color leftFade = (_isScrollable && _showLeftFade) ? Colors.transparent : Colors.black;
final Color rightFade = (_isScrollable && _showRightFade) ? Colors.transparent : Colors.black;
// Si ce n'est pas scrollable du tout, pas de fondu.
if (!_isScrollable) {
return LinearGradient(
colors: const <Color>[Colors.black, Colors.black, Colors.black, Colors.black],
stops: const [0.0, _fadeExtent, 1.0 - _fadeExtent, 1.0],
).createShader(bounds);
}
return LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: <Color>[
leftFade, // Bord gauche
Colors.black, // Devient opaque
Colors.black, // Reste opaque
rightFade // Bord droit
],
stops: const [0.0, _fadeExtent, 1.0 - _fadeExtent, 1.0], // 5% de fondu sur chaque bord
).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: Scrollbar( // Ajout du Scrollbar
controller: _scrollController, // Utiliser le même contrôleur
thumbVisibility: true, // Rendre la thumb toujours visible pour le web si souhaité, ou la laisser adaptative
child: ListView.builder(
controller: _scrollController,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 20.0),
itemCount: _childrenDataList.length + 1,
itemBuilder: (context, index) {
if (index < _childrenDataList.length) {
// Carte Enfant
return Padding(
padding: const EdgeInsets.only(right: 20.0), // Espace entre les cartes
child: _ChildCardWidget(
key: _childrenDataList[index].key, // Passer la clé unique
childData: _childrenDataList[index],
childIndex: index,
onPickImage: () => _pickImage(index),
onDateSelect: () => _selectDate(context, index),
onTogglePhotoConsent: (newValue) {
setState(() => _childrenDataList[index].photoConsent = newValue);
},
onToggleMultipleBirth: (newValue) {
setState(() => _childrenDataList[index].multipleBirth = newValue);
},
onToggleIsUnborn: (newValue) {
setState(() => _childrenDataList[index].isUnbornChild = newValue);
},
onRemove: () => _removeChild(_childrenDataList[index].key),
canBeRemoved: _childrenDataList.length > 1,
),
);
} else {
// Bouton Ajouter
return Center( // Pour centrer le bouton dans l'espace disponible
child: HoverReliefWidget(
onPressed: _addChild,
borderRadius: BorderRadius.circular(15),
child: Image.asset('assets/images/plus.png', height: 80, width: 80),
),
);
}
},
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Étape 3/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
const SizedBox(height: 10),
Text(
'Informations Enfants',
style: GoogleFonts.merienda(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87),
textAlign: TextAlign.center,
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 150.0),
child: SizedBox(
height: 684.0,
child: ShaderMask(
shaderCallback: (Rect bounds) {
final Color leftFade = (_isScrollable && _showLeftFade) ? Colors.transparent : Colors.black;
final Color rightFade = (_isScrollable && _showRightFade) ? Colors.transparent : Colors.black;
if (!_isScrollable) { return LinearGradient(colors: const <Color>[Colors.black, Colors.black, Colors.black, Colors.black], stops: const [0.0, _fadeExtent, 1.0 - _fadeExtent, 1.0],).createShader(bounds); }
return LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: <Color>[ leftFade, Colors.black, Colors.black, rightFade ], stops: const [0.0, _fadeExtent, 1.0 - _fadeExtent, 1.0], ).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: Scrollbar(
controller: _scrollController,
thumbVisibility: true,
child: ListView.builder(
controller: _scrollController,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 20.0),
itemCount: _registrationData.children.length + 1,
itemBuilder: (context, index) {
if (index < _registrationData.children.length) {
// Carte Enfant
return Padding(
padding: const EdgeInsets.only(right: 20.0),
child: _ChildCardWidget(
key: ValueKey(_registrationData.children[index].hashCode), // Utiliser une clé basée sur les données
childData: _registrationData.children[index],
childIndex: index,
onPickImage: () => _pickImage(index),
onDateSelect: () => _selectDate(context, index),
onFirstNameChanged: (value) => setState(() => _registrationData.children[index].firstName = value),
onLastNameChanged: (value) => setState(() => _registrationData.children[index].lastName = value),
onTogglePhotoConsent: (newValue) => setState(() => _registrationData.children[index].photoConsent = newValue),
onToggleMultipleBirth: (newValue) => setState(() => _registrationData.children[index].multipleBirth = newValue),
onToggleIsUnborn: (newValue) => setState(() {
_registrationData.children[index].isUnbornChild = newValue;
// Générer une nouvelle date si on change le statut
_registrationData.children[index].dob = DataGenerator.dob(isUnborn: newValue);
}),
onRemove: () => _removeChild(index),
canBeRemoved: _registrationData.children.length > 1,
),
);
} else {
// Bouton Ajouter
return Center(
child: HoverReliefWidget(
onPressed: _addChild,
borderRadius: BorderRadius.circular(15),
child: Image.asset('assets/images/plus.png', height: 80, width: 80),
),
);
}
},
),
),
),
),
const SizedBox(height: 20), // Espace optionnel après la liste
],
),
),
const SizedBox(height: 20),
],
),
),
// Chevrons de navigation
@@ -330,8 +275,8 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
child: IconButton(
icon: Image.asset('assets/images/chevron_right.png', height: 40),
onPressed: () {
print('Passer à l\'étape 4 (Situation familiale et CGU)');
Navigator.pushNamed(context, '/parent-register/step4');
// TODO: Validation (si nécessaire)
Navigator.pushNamed(context, '/parent-register/step4', arguments: _registrationData);
},
tooltip: 'Suivant',
),
@@ -342,24 +287,28 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
}
}
// Nouveau Widget pour la carte enfant
class _ChildCardWidget extends StatelessWidget {
final _ChildFormData childData;
final int childIndex; // Utile pour certains callbacks ou logging
// 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<String> onFirstNameChanged;
final ValueChanged<String> onLastNameChanged;
final ValueChanged<bool> onTogglePhotoConsent;
final ValueChanged<bool> onToggleMultipleBirth;
final ValueChanged<bool> onToggleIsUnborn;
final VoidCallback onRemove; // Callback pour supprimer la carte
final bool canBeRemoved; // Pour afficher/cacher le bouton de suppression
final VoidCallback onRemove;
final bool canBeRemoved;
const _ChildCardWidget({
required Key key, // Important pour le ListView.builder
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,
@@ -367,105 +316,158 @@ class _ChildCardWidget extends StatelessWidget {
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 = childData.imageFile;
final Color baseLavandeColor = Colors.purple.shade200;
final Color initialPhotoShadow = baseLavandeColor.withAlpha(90);
final Color hoverPhotoShadow = baseLavandeColor.withAlpha(130);
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: 300,
padding: const EdgeInsets.all(20),
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: const DecorationImage(image: AssetImage('assets/images/card_lavander.png'), fit: BoxFit.cover),
borderRadius: BorderRadius.circular(20),
image: DecorationImage(image: AssetImage(widget.childData.cardColor.path), fit: BoxFit.cover),
borderRadius: BorderRadius.circular(20 * 1.1), // 22
),
child: Stack( // Stack pour pouvoir superposer le bouton de suppression
child: Stack(
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
HoverReliefWidget(
onPressed: onPickImage,
onPressed: widget.onPickImage,
borderRadius: BorderRadius.circular(10),
initialShadowColor: initialPhotoShadow,
hoverShadowColor: hoverPhotoShadow,
child: SizedBox(
height: 100, width: 100,
height: 200.0,
width: 200.0,
child: Center(
child: Padding(
padding: const EdgeInsets.all(5.0),
padding: const EdgeInsets.all(5.0 * 1.1), // 5.5
child: currentChildImage != null
? ClipRRect(borderRadius: BorderRadius.circular(10), child: kIsWeb ? Image.network(currentChildImage.path, fit: BoxFit.cover) : Image.file(currentChildImage, fit: BoxFit.cover))
? 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: 5),
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: 14, fontWeight: FontWeight.w600)),
Switch(value: childData.isUnbornChild, onChanged: onToggleIsUnborn, activeColor: Theme.of(context).primaryColor),
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: 8),
const SizedBox(height: 9.0 * 1.1), // 9.9
CustomAppTextField(
controller: childData.firstNameController,
controller: _firstNameController,
labelText: 'Prénom',
hintText: 'Facultatif si à naître',
isRequired: !childData.isUnbornChild,
isRequired: !widget.childData.isUnbornChild,
fieldHeight: 55.0 * 1.1, // 60.5
),
const SizedBox(height: 5),
const SizedBox(height: 6.0 * 1.1), // 6.6
CustomAppTextField(
controller: childData.lastNameController,
controller: _lastNameController,
labelText: 'Nom',
hintText: 'Nom de l\'enfant',
enabled: true,
fieldHeight: 55.0 * 1.1, // 60.5
),
const SizedBox(height: 8),
const SizedBox(height: 9.0 * 1.1), // 9.9
CustomAppTextField(
controller: childData.dobController,
labelText: childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance',
controller: _dobController,
labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance',
hintText: 'JJ/MM/AAAA',
readOnly: true,
onTap: onDateSelect,
onTap: widget.onDateSelect,
suffixIcon: Icons.calendar_today,
fieldHeight: 55.0 * 1.1, // 60.5
),
const SizedBox(height: 10),
const SizedBox(height: 11.0 * 1.1), // 12.1
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppCustomCheckbox( // Utilisation du nouveau widget
AppCustomCheckbox(
label: 'Consentement photo',
value: childData.photoConsent,
onChanged: onTogglePhotoConsent,
value: widget.childData.photoConsent,
onChanged: widget.onTogglePhotoConsent,
checkboxSize: 22.0 * 1.1, // 24.2
),
const SizedBox(height: 5),
AppCustomCheckbox( // Utilisation du nouveau widget
const SizedBox(height: 6.0 * 1.1), // 6.6
AppCustomCheckbox(
label: 'Naissance multiple',
value: childData.multipleBirth,
onChanged: onToggleMultipleBirth,
value: widget.childData.multipleBirth,
onChanged: widget.onToggleMultipleBirth,
checkboxSize: 22.0 * 1.1, // 24.2
),
],
),
],
),
if (canBeRemoved) // Afficher le bouton de suppression conditionnellement
if (widget.canBeRemoved)
Positioned(
top: -5, // Ajuster pour le positionnement visuel
right: -5, // Ajuster pour le positionnement visuel
top: -5, right: -5,
child: InkWell(
onTap: onRemove,
customBorder: const CircleBorder(), // Pour un effet de clic circulaire
onTap: widget.onRemove,
customBorder: const CircleBorder(),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.8), // Fond rouge pour le bouton X
shape: BoxShape.circle,
),
decoration: BoxDecoration(color: Colors.red.withOpacity(0.8), shape: BoxShape.circle),
child: const Icon(Icons.close, color: Colors.white, size: 18),
),
),