import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron 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 // 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(); } } class ParentRegisterStep3Screen extends StatefulWidget { const ParentRegisterStep3Screen({super.key}); @override State createState() => _ParentRegisterStep3ScreenState(); } class _ParentRegisterStep3ScreenState extends State { // TODO: Gérer une liste d'enfants et leurs contrôleurs respectifs // List _children = [ChildData()]; // Commencer avec un enfant final _formKey = GlobalKey(); // 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 bool _isScrollable = false; bool _showLeftFade = false; bool _showRightFade = false; static const double _fadeExtent = 0.05; // Pourcentage de la vue pour le fondu (5%) @override void initState() { super.initState(); _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.dispose(); super.dispose(); } void _scrollListener() { if (!_scrollController.hasClients) return; // S'assurer que le controller est attaché 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) { setState(() { _isScrollable = newIsScrollable; _showLeftFade = newShowLeftFade; _showRightFade = newShowRightFade; }); } } void _addChild() { String initialLastName = ''; if (_childrenDataList.isNotEmpty) { initialLastName = _childrenDataList.first.lastNameController.text; } setState(() { _childrenDataList.add(_ChildFormData( key: UniqueKey(), initialLastName: initialLastName, )); }); // 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 if (_scrollController.hasClients && _scrollController.position.maxScrollExtent > 0.0) { _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) Future _pickImage(int childIndex) async { final ImagePicker picker = ImagePicker(); try { final XFile? pickedFile = await picker.pickImage( source: ImageSource.gallery, imageQuality: 70, maxWidth: 1024, maxHeight: 1024, ); if (pickedFile != null) { setState(() { if (childIndex < _childrenDataList.length) { _childrenDataList[childIndex].imageFile = File(pickedFile.path); } }); } // Fin de if (pickedFile != null) } catch (e) { print("Erreur lors de la sélection de l'image: $e"); } } Future _selectDate(BuildContext context, int childIndex) async { final _ChildFormData currentChild = _childrenDataList[childIndex]; final DateTime now = DateTime.now(); DateTime initialDatePickerDate = 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) { try { List parts = currentChild.dobController.text.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 */ } } } else { if (currentChild.dobController.text.isNotEmpty) { try { List parts = currentChild.dobController.text.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 */ } } } final DateTime? picked = await showDatePicker( 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}"; }); } } 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; return Scaffold( body: Stack( children: [ Positioned.fill( 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 [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: [ 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), ), ); } }, ), ), ), ), ), const SizedBox(height: 20), // Espace optionnel après la liste ], ), ), ), // Chevrons de navigation Positioned( top: screenSize.height / 2 - 20, left: 40, child: IconButton( icon: Transform(alignment: Alignment.center, transform: Matrix4.rotationY(math.pi), child: Image.asset('assets/images/chevron_right.png', height: 40)), onPressed: () => Navigator.pop(context), tooltip: 'Retour', ), ), Positioned( top: screenSize.height / 2 - 20, right: 40, child: IconButton( icon: Image.asset('assets/images/chevron_right.png', height: 40), onPressed: () { print('Passer à l\'étape 4 (Situation familiale)'); // Navigator.pushNamed(context, '/parent-register/step4'); }, tooltip: 'Suivant', ), ), ], ), ); } } // Nouveau Widget pour la carte enfant class _ChildCardWidget extends StatelessWidget { final _ChildFormData childData; final int childIndex; // Utile pour certains callbacks ou logging final VoidCallback onPickImage; final VoidCallback onDateSelect; final ValueChanged onTogglePhotoConsent; final ValueChanged onToggleMultipleBirth; final ValueChanged onToggleIsUnborn; final VoidCallback onRemove; // Callback pour supprimer la carte final bool canBeRemoved; // Pour afficher/cacher le bouton de suppression const _ChildCardWidget({ required Key key, // Important pour le ListView.builder required this.childData, required this.childIndex, required this.onPickImage, required this.onDateSelect, required this.onTogglePhotoConsent, required this.onToggleMultipleBirth, required this.onToggleIsUnborn, required this.onRemove, required this.canBeRemoved, }) : super(key: key); @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); return Container( width: 300, padding: const EdgeInsets.all(20), decoration: BoxDecoration( image: const DecorationImage(image: AssetImage('assets/images/card_lavander.png'), fit: BoxFit.cover), borderRadius: BorderRadius.circular(20), ), child: Stack( // Stack pour pouvoir superposer le bouton de suppression children: [ Column( mainAxisSize: MainAxisSize.min, children: [ HoverReliefWidget( onPressed: onPickImage, borderRadius: BorderRadius.circular(10), initialShadowColor: initialPhotoShadow, hoverShadowColor: hoverPhotoShadow, child: SizedBox( height: 100, width: 100, child: Center( child: Padding( padding: const EdgeInsets.all(5.0), child: currentChildImage != null ? ClipRRect(borderRadius: BorderRadius.circular(10), 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: 10), 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), ], ), const SizedBox(height: 15), CustomAppTextField( // Utilisation du nouveau widget controller: childData.firstNameController, label: 'Prénom', hintText: childData.isUnbornChild ? 'Prénom (optionnel)' : 'Prénom de l\'enfant', isRequired: !childData.isUnbornChild, ), const SizedBox(height: 10), CustomAppTextField( // Utilisation du nouveau widget controller: childData.lastNameController, label: 'Nom', hintText: 'Nom de l\'enfant', enabled: true, ), const SizedBox(height: 10), CustomAppTextField( // Utilisation du nouveau widget controller: childData.dobController, label: childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance', hintText: 'JJ/MM/AAAA', readOnly: true, onTap: onDateSelect, suffixIcon: Icons.calendar_today, ), const SizedBox(height: 18), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomCheckbox( // Utilisation du nouveau widget label: 'Consentement photo', value: childData.photoConsent, onChanged: onTogglePhotoConsent, ), const SizedBox(height: 10), AppCustomCheckbox( // Utilisation du nouveau widget label: 'Naissance multiple', value: childData.multipleBirth, onChanged: onToggleMultipleBirth, ), ], ), ], ), if (canBeRemoved) // Afficher le bouton de suppression conditionnellement Positioned( top: -5, // Ajuster pour le positionnement visuel right: -5, // Ajuster pour le positionnement visuel child: InkWell( onTap: onRemove, customBorder: const CircleBorder(), // Pour un effet de clic circulaire child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: Colors.red.withOpacity(0.8), // Fond rouge pour le bouton X shape: BoxShape.circle, ), child: const Icon(Icons.close, color: Colors.white, size: 18), ), ), ), ], ), ); } }