import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron import 'package:image_picker/image_picker.dart'; import 'dart:io' show File; import '../../widgets/hover_relief_widget.dart'; import '../../widgets/child_card_widget.dart'; import '../../widgets/custom_navigation_button.dart'; import '../../models/user_registration_data.dart'; import '../../utils/data_generator.dart'; import '../../models/card_assets.dart'; import '../../config/display_config.dart'; import 'package:provider/provider.dart'; import 'package:go_router/go_router.dart'; class ParentRegisterStep3Screen extends StatefulWidget { // final UserRegistrationData registrationData; // Supprimé const ParentRegisterStep3Screen({super.key /*, required this.registrationData */}); // Modifié @override _ParentRegisterStep3ScreenState createState() => _ParentRegisterStep3ScreenState(); } class _ParentRegisterStep3ScreenState extends State { // late UserRegistrationData _registrationData; // Supprimé 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 fondu // Liste ordonnée des couleurs de cartes pour les enfants static const List _childCardColors = [ CardColorVertical.lavender, // Premier enfant toujours lavande CardColorVertical.pink, CardColorVertical.peach, CardColorVertical.lime, CardColorVertical.red, CardColorVertical.green, CardColorVertical.blue, ]; // Garder une trace des couleurs déjà utilisées final Set _usedColors = {}; // Utilisation de GlobalKey pour les cartes enfants si validation complexe future // Map> _childFormKeys = {}; @override void initState() { super.initState(); final registrationData = Provider.of(context, listen: false); // _registrationData = registrationData; // Supprimé // Initialiser les couleurs utilisées avec les enfants existants for (var child in registrationData.children) { _usedColors.add(child.cardColor); } // S'il n'y a pas d'enfant, en ajouter un automatiquement APRÈS le premier build if (registrationData.children.isEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { _addChild(registrationData); }); } _scrollController.addListener(_scrollListener); WidgetsBinding.instance.addPostFrameCallback((_) => _scrollListener()); } @override void dispose() { _scrollController.removeListener(_scrollListener); _scrollController.dispose(); super.dispose(); } void _scrollListener() { if (!_scrollController.hasClients) return; final position = _scrollController.position; final newIsScrollable = position.maxScrollExtent > 0.0; final newShowLeftFade = newIsScrollable && position.pixels > (position.viewportDimension * _fadeExtent / 2); 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(UserRegistrationData registrationData) { // Prend registrationData setState(() { bool isUnborn = DataGenerator.boolean(); // Trouver la première couleur non utilisée CardColorVertical cardColor = _childCardColors.firstWhere( (color) => !_usedColors.contains(color), orElse: () => _childCardColors[0], // Fallback sur la première couleur si toutes sont utilisées ); final newChild = ChildData( lastName: registrationData.parent1.lastName, firstName: DataGenerator.firstName(), dob: DataGenerator.dob(isUnborn: isUnborn), isUnbornChild: isUnborn, photoConsent: DataGenerator.boolean(), multipleBirth: DataGenerator.boolean(), cardColor: cardColor, ); registrationData.addChild(newChild); _usedColors.add(cardColor); }); WidgetsBinding.instance.addPostFrameCallback((_) { _scrollListener(); if (_scrollController.hasClients && _scrollController.position.maxScrollExtent > 0.0) { _scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut); } }); } void _removeChild(int index, UserRegistrationData registrationData) { if (registrationData.children.length > 1 && index >= 0 && index < registrationData.children.length) { setState(() { // Ne pas retirer la couleur de _usedColors pour éviter sa réutilisation registrationData.children.removeAt(index); }); WidgetsBinding.instance.addPostFrameCallback((_) => _scrollListener()); } } Future _pickImage(int childIndex, UserRegistrationData registrationData) async { final ImagePicker picker = ImagePicker(); try { final XFile? pickedFile = await picker.pickImage( source: ImageSource.gallery, imageQuality: 70, maxWidth: 1024, maxHeight: 1024); if (pickedFile != null) { if (childIndex < registrationData.children.length) { final oldChild = registrationData.children[childIndex]; final updatedChild = ChildData( firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: oldChild.photoConsent, multipleBirth: oldChild.multipleBirth, isUnbornChild: oldChild.isUnbornChild, imageFile: File(pickedFile.path), cardColor: oldChild.cardColor, ); registrationData.updateChild(childIndex, updatedChild); } } } catch (e) { print("Erreur image: $e"); } } Future _selectDate(BuildContext context, int childIndex, UserRegistrationData registrationData) async { final ChildData currentChild = registrationData.children[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.dob.isNotEmpty) { try { List 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) {} } } else { if (currentChild.dob.isNotEmpty) { try { List 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) {} } } final DateTime? picked = await showDatePicker( context: context, initialDate: initialDatePickerDate, firstDate: firstDatePickerDate, lastDate: lastDatePickerDate, locale: const Locale('fr', 'FR'), ); if (picked != null) { final oldChild = registrationData.children[childIndex]; final updatedChild = ChildData( firstName: oldChild.firstName, lastName: oldChild.lastName, dob: "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}", photoConsent: oldChild.photoConsent, multipleBirth: oldChild.multipleBirth, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor, ); registrationData.updateChild(childIndex, updatedChild); } } @override Widget build(BuildContext context) { final registrationData = Provider.of(context /*, listen: true par défaut */); final screenSize = MediaQuery.of(context).size; final config = DisplayConfig.fromContext(context, mode: DisplayMode.editable); return Scaffold( body: Stack( children: [ Positioned.fill( child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat), ), config.isMobile ? _buildMobileLayout(context, config, screenSize, registrationData) : _buildDesktopLayout(context, config, screenSize, registrationData), // Chevrons desktop uniquement if (!config.isMobile) ...[ 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: () { if (context.canPop()) { context.pop(); } else { context.go('/parent-register-step2'); } }, tooltip: 'Retour', ), ), Positioned( top: screenSize.height / 2 - 20, right: 40, child: IconButton( icon: Image.asset('assets/images/chevron_right.png', height: 40), onPressed: () { context.go('/parent-register-step4'); }, tooltip: 'Suivant', ), ), ], ], ), ); } /// Layout MOBILE : Cartes empilées verticalement Widget _buildMobileLayout(BuildContext context, DisplayConfig config, Size screenSize, UserRegistrationData registrationData) { return Column( children: [ // Header fixe Padding( padding: const EdgeInsets.only(top: 20.0), child: Column( children: [ Text( 'Étape 3/5', style: GoogleFonts.merienda(fontSize: 13, color: Colors.black54), ), const SizedBox(height: 6), Text( 'Informations Enfants', style: GoogleFonts.merienda( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), ], ), ), const SizedBox(height: 16), // Liste scrollable des cartes + boutons Expanded( child: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.05), child: Column( children: [ // Générer les cartes enfants for (int index = 0; index < registrationData.children.length; index++) ...[ ChildCardWidget( key: ValueKey(registrationData.children[index].hashCode), childData: registrationData.children[index], childIndex: index, onPickImage: () => _pickImage(index, registrationData), onDateSelect: () => _selectDate(context, index, registrationData), onFirstNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData( firstName: value, lastName: registrationData.children[index].lastName, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent, multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor ))), onLastNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData( firstName: registrationData.children[index].firstName, lastName: value, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent, multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor ))), onTogglePhotoConsent: (newValue) { final oldChild = registrationData.children[index]; registrationData.updateChild(index, ChildData( firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: newValue, multipleBirth: oldChild.multipleBirth, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor )); }, onToggleMultipleBirth: (newValue) { final oldChild = registrationData.children[index]; registrationData.updateChild(index, ChildData( firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: oldChild.photoConsent, multipleBirth: newValue, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor )); }, onToggleIsUnborn: (newValue) { final oldChild = registrationData.children[index]; registrationData.updateChild(index, ChildData( firstName: oldChild.firstName, lastName: oldChild.lastName, dob: DataGenerator.dob(isUnborn: newValue), photoConsent: oldChild.photoConsent, multipleBirth: oldChild.multipleBirth, isUnbornChild: newValue, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor )); }, onRemove: () => _removeChild(index, registrationData), canBeRemoved: registrationData.children.length > 1, ), const SizedBox(height: 16), ], // Bouton "+" carré à la fin de la liste Center( child: HoverReliefWidget( onPressed: () => _addChild(registrationData), borderRadius: BorderRadius.circular(15), child: Container( width: 50, height: 50, child: Image.asset('assets/images/plus.png', fit: BoxFit.contain), ), ), ), const SizedBox(height: 30), // Boutons navigation en bas du scroll _buildMobileButtons(context, config, screenSize), const SizedBox(height: 20), ], ), ), ), ], ); } /// Layout DESKTOP : Scroll horizontal avec fondu Widget _buildDesktopLayout(BuildContext context, DisplayConfig config, Size screenSize, UserRegistrationData registrationData) { return Center( 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 [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, 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, registrationData), onDateSelect: () => _selectDate(context, index, registrationData), onFirstNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData( firstName: value, lastName: registrationData.children[index].lastName, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent, multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor ))), onLastNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData( firstName: registrationData.children[index].firstName, lastName: value, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent, multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor ))), onTogglePhotoConsent: (newValue) { final oldChild = registrationData.children[index]; registrationData.updateChild(index, ChildData( firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: newValue, multipleBirth: oldChild.multipleBirth, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor )); }, onToggleMultipleBirth: (newValue) { final oldChild = registrationData.children[index]; registrationData.updateChild(index, ChildData( firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: oldChild.photoConsent, multipleBirth: newValue, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor )); }, onToggleIsUnborn: (newValue) { final oldChild = registrationData.children[index]; registrationData.updateChild(index, ChildData( firstName: oldChild.firstName, lastName: oldChild.lastName, dob: DataGenerator.dob(isUnborn: newValue), photoConsent: oldChild.photoConsent, multipleBirth: oldChild.multipleBirth, isUnbornChild: newValue, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor )); }, onRemove: () => _removeChild(index, registrationData), canBeRemoved: registrationData.children.length > 1, ), ); } else { // Bouton Ajouter return Center( child: HoverReliefWidget( onPressed: () => _addChild(registrationData), borderRadius: BorderRadius.circular(15), child: Image.asset('assets/images/plus.png', height: 80, width: 80), ), ); } }, ), ), ), ), ), const SizedBox(height: 20), ], ), ); } /// Boutons navigation mobile Widget _buildMobileButtons(BuildContext context, DisplayConfig config, Size screenSize) { return Row( children: [ Expanded( child: HoverReliefWidget( child: CustomNavigationButton( text: 'Précédent', style: NavigationButtonStyle.purple, onPressed: () { if (context.canPop()) { context.pop(); } else { context.go('/parent-register-step2'); } }, width: double.infinity, height: 50, fontSize: 16, ), ), ), const SizedBox(width: 16), Expanded( child: HoverReliefWidget( child: CustomNavigationButton( text: 'Suivant', style: NavigationButtonStyle.green, onPressed: () { context.go('/parent-register-step4'); }, width: double.infinity, height: 50, fontSize: 16, ), ), ), ], ); } }