import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; import 'package:flutter/gestures.dart'; import '../../widgets/hover_relief_widget.dart'; import 'package:image_picker/image_picker.dart'; import 'dart:io' show File, Platform; import 'package:flutter/foundation.dart' show kIsWeb; import '../../widgets/custom_app_text_field.dart'; import '../../widgets/app_custom_checkbox.dart'; import '../../models/user_registration_data.dart'; import '../../utils/data_generator.dart'; import '../../models/card_assets.dart'; class ParentRegisterStep3Screen extends StatefulWidget { final UserRegistrationData registrationData; // Accepte les données const ParentRegisterStep3Screen({super.key, required this.registrationData}); @override State createState() => _ParentRegisterStep3ScreenState(); } class _ParentRegisterStep3ScreenState extends State { late UserRegistrationData _registrationData; final ScrollController _scrollController = ScrollController(); bool _isScrollable = false; bool _showLeftFade = false; bool _showRightFade = false; static const double _fadeExtent = 0.05; static const List _childCardColors = [ CardColorVertical.lavender, CardColorVertical.pink, CardColorVertical.peach, CardColorVertical.lime, CardColorVertical.red, CardColorVertical.green, CardColorVertical.blue, ]; final Set _usedColors = {}; @override void initState() { super.initState(); _registrationData = widget.registrationData; // 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 avec des données générées if (_registrationData.children.isEmpty) { _addChild(); } _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() { 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), gender: DataGenerator.gender(), 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) { 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) 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 < _registrationData.children.length) { _registrationData.children[childIndex].imageFile = File(pickedFile.path); } }); } } catch (e) { // Gestion silencieuse de l'erreur } } Future _selectDate(BuildContext context, int childIndex) 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) { setState(() { currentChild.dob = "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}"; }); } } @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: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Étape 3/6', 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), 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), onGenderChanged: (value) => setState(() => _registrationData.children[index].gender = 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; _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), ], ), ), // 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: () { // TODO: Validation (si nécessaire) Navigator.pushNamed(context, '/parent-register/step4', arguments: _registrationData); }, tooltip: 'Suivant', ), ), ], ), ); } } // Widget pour la carte enfant (adapté pour prendre ChildData et des callbacks) class _ChildCardWidget extends StatefulWidget { final ChildData childData; final int childIndex; final VoidCallback onPickImage; final VoidCallback onDateSelect; final ValueChanged onFirstNameChanged; final ValueChanged onLastNameChanged; final ValueChanged onGenderChanged; 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.onGenderChanged, 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: 8.0 * 1.1), 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: 6.0 * 1.1) 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: 6.0 * 1.1), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Radio( value: 'F', groupValue: widget.childData.gender, onChanged: (value) => widget.onGenderChanged(value!), activeColor: Theme.of(context).primaryColor, ), Text('Fille', style: GoogleFonts.merienda(fontSize: 16 * 1.1)), const SizedBox(width: 20), Radio( value: 'H', groupValue: widget.childData.gender, onChanged: (value) => widget.onGenderChanged(value!), activeColor: Theme.of(context).primaryColor, ), Text('Garçon', style: GoogleFonts.merienda(fontSize: 16 * 1.1)), ], ), const SizedBox(height: 6.0 * 1.1) 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: 8.0 * 1.1), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomCheckbox( label: 'Consentement photo', value: widget.childData.photoConsent, onChanged: widget.onTogglePhotoConsent, checkboxSize: 22.0 * 1.1, ), const SizedBox(height: 4.0 * 1.1), AppCustomCheckbox( label: 'Naissance multiple', value: widget.childData.multipleBirth, onChanged: widget.onToggleMultipleBirth, checkboxSize: 22.0 * 1.1, ), ], ), ], ), 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, ), ), ), ], ), ); } }