import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:go_router/go_router.dart'; import 'dart:math' as math; import 'custom_app_text_field.dart'; import 'form_field_wrapper.dart'; import 'hover_relief_widget.dart'; import 'custom_navigation_button.dart'; import '../models/card_assets.dart'; import '../config/display_config.dart'; /// Modèle de données pour le formulaire class PersonalInfoData { String firstName; String lastName; String phone; String email; String address; String postalCode; String city; PersonalInfoData({ this.firstName = '', this.lastName = '', this.phone = '', this.email = '', this.address = '', this.postalCode = '', this.city = '', }); } /// Widget générique pour les formulaires d'informations personnelles /// Supporte mode éditable et readonly, responsive mobile/desktop class PersonalInfoFormScreen extends StatefulWidget { final DisplayMode mode; // editable ou readonly final String stepText; // Ex: "Étape 1/5" final String title; // Ex: "Informations du Parent Principal" final CardColorHorizontal cardColor; final PersonalInfoData initialData; final Function(PersonalInfoData data, {bool? hasSecondPerson, bool? sameAddress}) onSubmit; final String previousRoute; // Options spécifiques pour Parent 2 final bool showSecondPersonToggle; // Afficher "Il y a un 2ème parent" final bool? initialHasSecondPerson; final bool showSameAddressCheckbox; // Afficher "Même adresse que parent 1" final bool? initialSameAddress; final PersonalInfoData? referenceAddressData; // Pour pré-remplir si "même adresse" final bool embedContentOnly; // Si true, affiche seulement la carte (sans scaffold/fond/titre) final VoidCallback? onEdit; // Callback pour le bouton d'édition (si affiché) const PersonalInfoFormScreen({ super.key, this.mode = DisplayMode.editable, required this.stepText, required this.title, required this.cardColor, required this.initialData, required this.onSubmit, required this.previousRoute, this.showSecondPersonToggle = false, this.initialHasSecondPerson, this.showSameAddressCheckbox = false, this.initialSameAddress, this.referenceAddressData, this.embedContentOnly = false, this.onEdit, }); @override State createState() => _PersonalInfoFormScreenState(); } class _PersonalInfoFormScreenState extends State { final _formKey = GlobalKey(); late TextEditingController _lastNameController; late TextEditingController _firstNameController; late TextEditingController _phoneController; late TextEditingController _emailController; late TextEditingController _addressController; late TextEditingController _postalCodeController; late TextEditingController _cityController; bool _hasSecondPerson = false; bool _sameAddress = false; bool _fieldsEnabled = true; @override void initState() { super.initState(); _lastNameController = TextEditingController(text: widget.initialData.lastName); _firstNameController = TextEditingController(text: widget.initialData.firstName); _phoneController = TextEditingController(text: widget.initialData.phone); _emailController = TextEditingController(text: widget.initialData.email); _addressController = TextEditingController(text: widget.initialData.address); _postalCodeController = TextEditingController(text: widget.initialData.postalCode); _cityController = TextEditingController(text: widget.initialData.city); if (widget.showSecondPersonToggle) { _hasSecondPerson = widget.initialHasSecondPerson ?? true; _fieldsEnabled = _hasSecondPerson; } if (widget.showSameAddressCheckbox) { _sameAddress = widget.initialSameAddress ?? false; _updateAddressFields(); } } @override void dispose() { _lastNameController.dispose(); _firstNameController.dispose(); _phoneController.dispose(); _emailController.dispose(); _addressController.dispose(); _postalCodeController.dispose(); _cityController.dispose(); super.dispose(); } void _updateAddressFields() { if (_sameAddress && widget.referenceAddressData != null) { _addressController.text = widget.referenceAddressData!.address; _postalCodeController.text = widget.referenceAddressData!.postalCode; _cityController.text = widget.referenceAddressData!.city; } } void _handleSubmit() { if (widget.mode == DisplayMode.readonly || _formKey.currentState!.validate()) { final data = PersonalInfoData( firstName: _firstNameController.text, lastName: _lastNameController.text, phone: _phoneController.text, email: _emailController.text, address: _addressController.text, postalCode: _postalCodeController.text, city: _cityController.text, ); widget.onSubmit( data, hasSecondPerson: widget.showSecondPersonToggle ? _hasSecondPerson : null, sameAddress: widget.showSameAddressCheckbox ? _sameAddress : null, ); } } @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; final config = DisplayConfig.fromContext(context, mode: widget.mode); if (widget.embedContentOnly) { return _buildCard(context, config, screenSize); } 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( widget.stepText, style: GoogleFonts.merienda( fontSize: config.isMobile ? 13 : 16, color: Colors.black54, ), ), SizedBox(height: config.isMobile ? 6 : 10), Text( widget.title, style: GoogleFonts.merienda( fontSize: config.isMobile ? 18 : 24, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), SizedBox(height: config.isMobile ? 16 : 30), _buildCard(context, config, screenSize), // Boutons mobile sous la carte (dans le scroll) if (config.isMobile) ...[ const SizedBox(height: 20), Padding( padding: EdgeInsets.symmetric( horizontal: screenSize.width * 0.05, // Même marge que la carte (0.9 = 0.05 de chaque côté) ), child: Row( children: [ Expanded( child: HoverReliefWidget( child: CustomNavigationButton( text: 'Précédent', style: NavigationButtonStyle.purple, onPressed: () { if (context.canPop()) { context.pop(); } else { context.go(widget.previousRoute); } }, width: double.infinity, height: 50, fontSize: 16, ), ), ), const SizedBox(width: 16), // Écart entre les boutons Expanded( child: HoverReliefWidget( child: CustomNavigationButton( text: 'Suivant', style: NavigationButtonStyle.green, onPressed: _handleSubmit, width: double.infinity, height: 50, fontSize: 16, ), ), ), ], ), ), const SizedBox(height: 10), ], ], ), ), ), // Chevrons de navigation (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(widget.previousRoute); } }, tooltip: 'Retour', ), ), Positioned( top: screenSize.height / 2 - 20, right: 40, child: IconButton( icon: Image.asset('assets/images/chevron_right.png', height: 40), onPressed: _handleSubmit, tooltip: 'Suivant', ), ), ], ], ), ); } Widget _buildCard(BuildContext context, DisplayConfig config, Size screenSize) { // En mode readonly desktop avec embedContentOnly, utiliser le layout ancien (AspectRatio 2:1) if (config.isReadonly && !config.isMobile && widget.embedContentOnly) { return _buildReadonlyDesktopCard(context, config, screenSize); } // En mode readonly mobile avec embedContentOnly, forcer le ratio 1:2 (Vertical) if (config.isReadonly && config.isMobile && widget.embedContentOnly) { return Padding( padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.05), // Marge 5% child: _buildMobileReadonlyCard(context, config, screenSize), ); } // Mode normal (éditable ou mobile non-récap) return Stack( clipBehavior: Clip.none, children: [ Container( width: config.isMobile ? screenSize.width * 0.9 : screenSize.width * 0.6, padding: EdgeInsets.symmetric( vertical: config.isMobile ? 20 : (config.isReadonly ? 30 : 50), horizontal: config.isMobile ? 24 : 50, ), decoration: BoxDecoration( image: DecorationImage( image: AssetImage( config.isMobile ? _getVerticalCardAsset() : widget.cardColor.path ), fit: BoxFit.fill, ), ), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ if (widget.embedContentOnly) ...[ Text( // Titre raccourci sur mobile en mode readonly pour laisser place au bouton edit (config.isMobile && config.isReadonly && widget.title.contains('Parent Principal')) ? 'Parent Principal' : widget.title, style: GoogleFonts.merienda( fontSize: config.isMobile ? 18 : 22, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), const SizedBox(height: 20), ], if (config.isEditable && widget.showSecondPersonToggle) _buildToggles(context, config), _buildFormFields(context, config), ], ), ), ), if (config.isReadonly && widget.onEdit != null) Positioned( top: 10, right: 10, child: IconButton( icon: const Icon(Icons.edit, color: Colors.black54, size: 28), onPressed: widget.onEdit, tooltip: 'Modifier', ), ), ], ); } /// Carte en mode readonly MOBILE avec hauteur adaptative Widget _buildMobileReadonlyCard(BuildContext context, DisplayConfig config, Size screenSize) { return Container( width: double.infinity, // Pas de height fixe, s'adapte au contenu padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 24.0), decoration: BoxDecoration( image: DecorationImage( image: AssetImage(_getVerticalCardAsset()), fit: BoxFit.fill, // Fill pour que l'image s'étire selon la hauteur du contenu ), borderRadius: BorderRadius.circular(15), ), child: Stack( children: [ Column( mainAxisSize: MainAxisSize.min, // Important : prend le min de place children: [ // Titre + Edit Button Row( children: [ Expanded( child: Text( (widget.title.contains('Parent Principal') ? 'Parent Principal' : (widget.title.contains('Deuxième Parent') ? 'Deuxième Parent' : widget.title)), style: GoogleFonts.merienda(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87), textAlign: TextAlign.center, ), ), if (widget.onEdit != null) const SizedBox(width: 28), ], ), // Contenu aligné en haut (plus d'Expanded ici car parent non contraint) Padding( padding: const EdgeInsets.only(top: 20.0), child: _buildFormFields(context, config), ), ], ), if (widget.onEdit != null) Positioned( top: 0, right: 0, child: IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(), icon: const Icon(Icons.edit, color: Colors.black54, size: 24), onPressed: widget.onEdit, tooltip: 'Modifier', ), ), ], ), ); } /// Carte en mode readonly desktop avec AspectRatio 2:1 (format de l'ancien récapitulatif) Widget _buildReadonlyDesktopCard(BuildContext context, DisplayConfig config, Size screenSize) { // Largeur de la carte : 50% de l'écran (comme l'ancien système) final cardWidth = screenSize.width / 2.0; return SizedBox( width: cardWidth, child: AspectRatio( aspectRatio: 2.0, child: Container( padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 25.0), decoration: BoxDecoration( image: DecorationImage( image: AssetImage(widget.cardColor.path), fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(15), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Titre centré Align( alignment: Alignment.center, child: Text( widget.title, style: GoogleFonts.merienda( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), const SizedBox(height: 55), // Espace très augmenté pour pousser les champs vers le bas // Champs du formulaire _buildFormFields(context, config), ], ), ), // Bouton d'édition à droite if (widget.onEdit != null) IconButton( icon: const Icon(Icons.edit, color: Colors.black54, size: 28), onPressed: widget.onEdit, tooltip: 'Modifier', ), ], ), ), ), ); } /// Construit les toggles (Parent 2 / Même adresse) Widget _buildToggles(BuildContext context, DisplayConfig config) { if (config.isMobile) { // Layout vertical sur mobile - toggles compacts return Column( children: [ _buildSecondPersonToggle(context, config), if (widget.showSameAddressCheckbox) ...[ const SizedBox(height: 5), // Réduit de 12 à 5 _buildSameAddressToggle(context, config), ], const SizedBox(height: 10), // Réduit de 24 à 10 ], ); } else { // Layout horizontal sur desktop return Column( children: [ Row( children: [ Expanded( flex: 12, child: _buildSecondPersonToggle(context, config), ), const Expanded(flex: 1, child: SizedBox()), if (widget.showSameAddressCheckbox) Expanded( flex: 12, child: _buildSameAddressToggle(context, config), ), ], ), const SizedBox(height: 32), ], ); } } Widget _buildSecondPersonToggle(BuildContext context, DisplayConfig config) { return Row( children: [ Icon(Icons.person_add_alt_1, size: config.isMobile ? 18 : 20), SizedBox(width: config.isMobile ? 6 : 8), Expanded( child: Text( 'Ajouter Parent 2 ?', style: GoogleFonts.merienda( fontWeight: FontWeight.bold, fontSize: config.isMobile ? 14 : 16, ), overflow: TextOverflow.ellipsis, ), ), Transform.scale( scale: config.isMobile ? 0.85 : 1.0, child: Switch( value: _hasSecondPerson, onChanged: (value) { setState(() { _hasSecondPerson = value; _fieldsEnabled = value; }); }, activeColor: Theme.of(context).primaryColor, ), ), ], ); } Widget _buildSameAddressToggle(BuildContext context, DisplayConfig config) { return Row( children: [ Icon( Icons.home_work_outlined, size: config.isMobile ? 18 : 20, color: _fieldsEnabled ? null : Colors.grey, ), SizedBox(width: config.isMobile ? 6 : 8), Expanded( child: Text( 'Même Adresse ?', style: GoogleFonts.merienda( color: _fieldsEnabled ? null : Colors.grey, fontSize: config.isMobile ? 14 : 16, ), overflow: TextOverflow.ellipsis, ), ), Transform.scale( scale: config.isMobile ? 0.85 : 1.0, child: Switch( value: _sameAddress, onChanged: _fieldsEnabled ? (value) { setState(() { _sameAddress = value; _updateAddressFields(); }); } : null, activeColor: Theme.of(context).primaryColor, ), ), ], ); } /// Construit les champs du formulaire avec la nouvelle infrastructure Widget _buildFormFields(BuildContext context, DisplayConfig config) { if (config.isMobile) { return _buildMobileFields(context, config); } else { return _buildDesktopFields(context, config); } } /// Layout DESKTOP : champs côte à côte (horizontal) Widget _buildDesktopFields(BuildContext context, DisplayConfig config) { // En mode readonly, utiliser l'ancien layout du récapitulatif if (config.isReadonly) { return _buildReadonlyDesktopFields(context); } // Mode éditable : layout normal final double verticalSpacing = 32.0; return Column( children: [ // Nom et Prénom Row( children: [ Expanded( child: _buildField( config: config, label: 'Nom', controller: _lastNameController, hint: 'Votre nom de famille', enabled: _fieldsEnabled, ), ), const SizedBox(width: 20), Expanded( child: _buildField( config: config, label: 'Prénom', controller: _firstNameController, hint: 'Votre prénom', enabled: _fieldsEnabled, ), ), ], ), SizedBox(height: verticalSpacing), // Téléphone et Email Row( children: [ Expanded( child: _buildField( config: config, label: 'Téléphone', controller: _phoneController, hint: 'Votre numéro de téléphone', keyboardType: TextInputType.phone, enabled: _fieldsEnabled, ), ), const SizedBox(width: 20), Expanded( child: _buildField( config: config, label: 'Email', controller: _emailController, hint: 'Votre adresse e-mail', keyboardType: TextInputType.emailAddress, enabled: _fieldsEnabled, ), ), ], ), SizedBox(height: verticalSpacing), // Adresse _buildField( config: config, label: 'Adresse (N° et Rue)', controller: _addressController, hint: 'Numéro et nom de votre rue', enabled: _fieldsEnabled && !_sameAddress, ), SizedBox(height: verticalSpacing), // Code Postal et Ville Row( children: [ Expanded( flex: 2, child: _buildField( config: config, label: 'Code Postal', controller: _postalCodeController, hint: 'Code postal', keyboardType: TextInputType.number, enabled: _fieldsEnabled && !_sameAddress, ), ), const SizedBox(width: 20), Expanded( flex: 5, child: _buildField( config: config, label: 'Ville', controller: _cityController, hint: 'Votre ville', enabled: _fieldsEnabled && !_sameAddress, ), ), ], ), ], ); } /// Layout DESKTOP en mode READONLY : réplique l'ancien design du récapitulatif Widget _buildReadonlyDesktopFields(BuildContext context) { const double verticalSpacing = 20.0; // Réduit pour compacter le bas const double labelFontSize = 22.0; const double valueFontSize = 18.0; // Taille du texte dans les champs return Column( children: [ // Ligne 1 : Nom + Prénom Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: _buildDisplayFieldValue( context, 'Nom :', _lastNameController.text, labelFontSize: labelFontSize, valueFontSize: valueFontSize, ), ), const SizedBox(width: 20), Expanded( child: _buildDisplayFieldValue( context, 'Prénom :', _firstNameController.text, labelFontSize: labelFontSize, valueFontSize: valueFontSize, ), ), ], ), const SizedBox(height: verticalSpacing), // Ligne 2 : Téléphone + Email Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: _buildDisplayFieldValue( context, 'Téléphone :', _phoneController.text, labelFontSize: labelFontSize, valueFontSize: valueFontSize, ), ), const SizedBox(width: 20), Expanded( child: _buildDisplayFieldValue( context, 'Email :', _emailController.text, multiLine: true, labelFontSize: labelFontSize, valueFontSize: valueFontSize, ), ), ], ), const SizedBox(height: verticalSpacing), // Ligne 3 : Adresse complète (adresse + CP + ville) _buildDisplayFieldValue( context, 'Adresse :', "${_addressController.text}\n${_postalCodeController.text} ${_cityController.text}".trim(), multiLine: true, fieldHeight: 80, labelFontSize: labelFontSize, valueFontSize: valueFontSize, ), ], ); } /// Helper pour afficher un champ en lecture seule avec le style de l'ancien récap Widget _buildDisplayFieldValue( BuildContext context, String label, String value, { bool multiLine = false, double fieldHeight = 50.0, double labelFontSize = 18.0, double valueFontSize = 18.0, // Taille du texte dans le champ }) { const FontWeight labelFontWeight = FontWeight.w600; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: GoogleFonts.merienda( fontSize: labelFontSize, fontWeight: labelFontWeight, ), ), const SizedBox(height: 4), Container( width: double.infinity, height: multiLine ? null : fieldHeight, constraints: multiLine ? const BoxConstraints(minHeight: 50.0) : null, padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), decoration: BoxDecoration( image: const DecorationImage( image: AssetImage('assets/images/bg_beige.png'), fit: BoxFit.fill, ), borderRadius: BorderRadius.circular(8), ), child: Text( value.isNotEmpty ? value : '-', style: GoogleFonts.merienda(fontSize: valueFontSize), maxLines: multiLine ? null : 1, overflow: multiLine ? TextOverflow.visible : TextOverflow.ellipsis, ), ), ], ); } /// Layout MOBILE : tous les champs empilés verticalement Widget _buildMobileFields(BuildContext context, DisplayConfig config) { // Mode Readonly Mobile : Layout compact et groupé if (config.isReadonly) { // NOTE: FormFieldWrapper ajoute déjà un padding vertical (8px mobile), // donc on n'ajoute pas de SizedBox supplémentaire ici pour éviter un double espacement. return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildField(config: config, label: 'Nom', controller: _lastNameController), _buildField(config: config, label: 'Prénom', controller: _firstNameController), _buildField(config: config, label: 'Téléphone', controller: _phoneController), _buildField(config: config, label: 'Email', controller: _emailController), // Adresse complète en un seul bloc multiligne FormFieldWrapper( config: config, label: 'Adresse', value: "${_addressController.text}\n${_postalCodeController.text} ${_cityController.text}".trim(), maxLines: 3, ), ], ); } // Mode Editable Mobile : Layout standard avec champs séparés return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Nom _buildField( config: config, label: 'Nom', controller: _lastNameController, hint: 'Votre nom de famille', enabled: _fieldsEnabled, ), const SizedBox(height: 12), // Prénom _buildField( config: config, label: 'Prénom', controller: _firstNameController, hint: 'Votre prénom', enabled: _fieldsEnabled, ), const SizedBox(height: 12), // Téléphone _buildField( config: config, label: 'Téléphone', controller: _phoneController, hint: 'Votre numéro de téléphone', keyboardType: TextInputType.phone, enabled: _fieldsEnabled, ), const SizedBox(height: 12), // Email _buildField( config: config, label: 'Email', controller: _emailController, hint: 'Votre adresse e-mail', keyboardType: TextInputType.emailAddress, enabled: _fieldsEnabled, ), const SizedBox(height: 12), // Adresse _buildField( config: config, label: 'Adresse (N° et Rue)', controller: _addressController, hint: 'Numéro et nom de votre rue', enabled: _fieldsEnabled && !_sameAddress, ), const SizedBox(height: 12), // Code Postal _buildField( config: config, label: 'Code Postal', controller: _postalCodeController, hint: 'Code postal', keyboardType: TextInputType.number, enabled: _fieldsEnabled && !_sameAddress, ), const SizedBox(height: 12), // Ville _buildField( config: config, label: 'Ville', controller: _cityController, hint: 'Votre ville', enabled: _fieldsEnabled && !_sameAddress, ), ], ); } /// Construit un champ individuel (éditable ou readonly) Widget _buildField({ required DisplayConfig config, required String label, required TextEditingController controller, String? hint, TextInputType? keyboardType, bool enabled = true, }) { if (config.isReadonly) { // Mode readonly : utiliser FormFieldWrapper return FormFieldWrapper( config: config, label: label, value: controller.text, ); } else { // Mode éditable : style adapté mobile/desktop return CustomAppTextField( controller: controller, labelText: label, hintText: hint ?? label, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, fieldHeight: config.isMobile ? 45.0 : 53.0, labelFontSize: config.isMobile ? 15.0 : 22.0, inputFontSize: config.isMobile ? 14.0 : 20.0, keyboardType: keyboardType ?? TextInputType.text, enabled: enabled, ); } } /// Retourne l'asset de carte vertical correspondant à la couleur String _getVerticalCardAsset() { switch (widget.cardColor) { case CardColorHorizontal.blue: return CardColorVertical.blue.path; case CardColorHorizontal.green: return CardColorVertical.green.path; case CardColorHorizontal.lavender: return CardColorVertical.lavender.path; case CardColorHorizontal.lime: return CardColorVertical.lime.path; case CardColorHorizontal.peach: return CardColorVertical.peach.path; case CardColorHorizontal.pink: return CardColorVertical.pink.path; case CardColorHorizontal.red: return CardColorVertical.red.path; } } }