diff --git a/frontend/assets/cards/card_blue.png b/frontend/assets/cards/card_blue.png new file mode 100644 index 0000000..724e6b1 Binary files /dev/null and b/frontend/assets/cards/card_blue.png differ diff --git a/frontend/assets/cards/card_blue_h.png b/frontend/assets/cards/card_blue_h.png new file mode 100644 index 0000000..1c9b3a0 Binary files /dev/null and b/frontend/assets/cards/card_blue_h.png differ diff --git a/frontend/assets/cards/card_green.png b/frontend/assets/cards/card_green.png new file mode 100644 index 0000000..bbb05ac Binary files /dev/null and b/frontend/assets/cards/card_green.png differ diff --git a/frontend/assets/cards/card_green_h.png b/frontend/assets/cards/card_green_h.png new file mode 100644 index 0000000..47e9e1b Binary files /dev/null and b/frontend/assets/cards/card_green_h.png differ diff --git a/frontend/assets/cards/card_lavender.png b/frontend/assets/cards/card_lavender.png new file mode 100644 index 0000000..315bc5c Binary files /dev/null and b/frontend/assets/cards/card_lavender.png differ diff --git a/frontend/assets/cards/card_lavender_h.png b/frontend/assets/cards/card_lavender_h.png new file mode 100644 index 0000000..db1e761 Binary files /dev/null and b/frontend/assets/cards/card_lavender_h.png differ diff --git a/frontend/assets/cards/card_lime.png b/frontend/assets/cards/card_lime.png new file mode 100644 index 0000000..d0f48e6 Binary files /dev/null and b/frontend/assets/cards/card_lime.png differ diff --git a/frontend/assets/cards/card_lime_h.png b/frontend/assets/cards/card_lime_h.png new file mode 100644 index 0000000..04da45c Binary files /dev/null and b/frontend/assets/cards/card_lime_h.png differ diff --git a/frontend/assets/cards/card_peach.png b/frontend/assets/cards/card_peach.png new file mode 100644 index 0000000..da3bf04 Binary files /dev/null and b/frontend/assets/cards/card_peach.png differ diff --git a/frontend/assets/cards/card_peach_h.png b/frontend/assets/cards/card_peach_h.png new file mode 100644 index 0000000..159ef8c Binary files /dev/null and b/frontend/assets/cards/card_peach_h.png differ diff --git a/frontend/assets/cards/card_pink.png b/frontend/assets/cards/card_pink.png new file mode 100644 index 0000000..c44bb50 Binary files /dev/null and b/frontend/assets/cards/card_pink.png differ diff --git a/frontend/assets/cards/card_pink_h.png b/frontend/assets/cards/card_pink_h.png new file mode 100644 index 0000000..caf78cf Binary files /dev/null and b/frontend/assets/cards/card_pink_h.png differ diff --git a/frontend/assets/cards/card_red.png b/frontend/assets/cards/card_red.png new file mode 100644 index 0000000..0c09d51 Binary files /dev/null and b/frontend/assets/cards/card_red.png differ diff --git a/frontend/assets/cards/card_red_h.png b/frontend/assets/cards/card_red_h.png new file mode 100644 index 0000000..a1ff236 Binary files /dev/null and b/frontend/assets/cards/card_red_h.png differ diff --git a/frontend/assets/images/card_lavander_h.png b/frontend/assets/images/card_lavander_h.png index 430790d..3675a75 100644 Binary files a/frontend/assets/images/card_lavander_h.png and b/frontend/assets/images/card_lavander_h.png differ diff --git a/frontend/assets/images/card_rose_h.png b/frontend/assets/images/card_rose_h.png new file mode 100644 index 0000000..7263feb Binary files /dev/null and b/frontend/assets/images/card_rose_h.png differ diff --git a/frontend/lib/models/card_assets.dart b/frontend/lib/models/card_assets.dart new file mode 100644 index 0000000..c86b663 --- /dev/null +++ b/frontend/lib/models/card_assets.dart @@ -0,0 +1,25 @@ +enum CardColorVertical { + red('assets/cards/card_red.png'), + pink('assets/cards/card_pink.png'), + peach('assets/cards/card_peach.png'), + lime('assets/cards/card_lime.png'), + lavender('assets/cards/card_lavender.png'), + green('assets/cards/card_green.png'), + blue('assets/cards/card_blue.png'); + + final String path; + const CardColorVertical(this.path); +} + +enum CardColorHorizontal { + red('assets/cards/card_red_h.png'), + pink('assets/cards/card_pink_h.png'), + peach('assets/cards/card_peach_h.png'), + lime('assets/cards/card_lime_h.png'), + lavender('assets/cards/card_lavender_h.png'), + green('assets/cards/card_green_h.png'), + blue('assets/cards/card_blue_h.png'); + + final String path; + const CardColorHorizontal(this.path); +} \ No newline at end of file diff --git a/frontend/lib/models/placeholder_registration_data.dart b/frontend/lib/models/placeholder_registration_data.dart deleted file mode 100644 index 18b67bb..0000000 --- a/frontend/lib/models/placeholder_registration_data.dart +++ /dev/null @@ -1,18 +0,0 @@ -// frontend/lib/models/placeholder_registration_data.dart -class PlaceholderRegistrationData { - final String? parent1Name; - // Ajoutez ici d'autres champs au fur et à mesure que nous définissons les données nécessaires - // pour parent 1, parent 2, enfants, motivation - - // Exemple de champ pour savoir si le parent 2 existe - final bool parent2Exists; - final List childrenNames; // Juste un exemple, à remplacer par une vraie structure enfant - final String? motivationText; - - PlaceholderRegistrationData({ - this.parent1Name, - this.parent2Exists = false, // Valeur par défaut - this.childrenNames = const [], // Valeur par défaut - this.motivationText, - }); -} \ No newline at end of file diff --git a/frontend/lib/models/user_registration_data.dart b/frontend/lib/models/user_registration_data.dart new file mode 100644 index 0000000..d2c6954 --- /dev/null +++ b/frontend/lib/models/user_registration_data.dart @@ -0,0 +1,97 @@ +import 'dart:io'; // Pour File +import '../models/card_assets.dart'; // Import de l'enum CardColorVertical + +class ParentData { + String firstName; + String lastName; + String address; // Rue et numéro + String postalCode; // Ajout + String city; // Ajout + String phone; + String email; + String password; // Peut-être pas nécessaire pour le récap, mais pour la création initiale si + File? profilePicture; // Chemin ou objet File + + ParentData({ + this.firstName = '', + this.lastName = '', + this.address = '', // Rue + this.postalCode = '', // Ajout + this.city = '', // Ajout + this.phone = '', + this.email = '', + this.password = '', + this.profilePicture, + }); +} + +class ChildData { + String firstName; + String lastName; + String dob; // Date de naissance ou prévisionnelle + bool photoConsent; + bool multipleBirth; + bool isUnbornChild; + File? imageFile; + CardColorVertical cardColor; // Nouveau champ pour la couleur de la carte + + ChildData({ + this.firstName = '', + this.lastName = '', + this.dob = '', + this.photoConsent = false, + this.multipleBirth = false, + this.isUnbornChild = false, + this.imageFile, + required this.cardColor, // Rendre requis dans le constructeur + }); +} + +class UserRegistrationData { + ParentData parent1; + ParentData? parent2; // Optionnel + List children; + String motivationText; + bool cguAccepted; + + UserRegistrationData({ + ParentData? parent1Data, + this.parent2, + List? childrenData, + this.motivationText = '', + this.cguAccepted = false, + }) : parent1 = parent1Data ?? ParentData(), + children = childrenData ?? []; + + // Méthode pour ajouter/mettre à jour le parent 1 + void updateParent1(ParentData data) { + parent1 = data; + } + + // Méthode pour ajouter/mettre à jour le parent 2 + void updateParent2(ParentData? data) { + parent2 = data; + } + + // Méthode pour ajouter un enfant + void addChild(ChildData child) { + children.add(child); + } + + // Méthode pour mettre à jour un enfant (si nécessaire plus tard) + void updateChild(int index, ChildData child) { + if (index >= 0 && index < children.length) { + children[index] = child; + } + } + + // Mettre à jour la motivation + void updateMotivation(String text) { + motivationText = text; + } + + // Accepter les CGU + void acceptCGU() { + cguAccepted = true; + } +} \ No newline at end of file diff --git a/frontend/lib/navigation/app_router.dart b/frontend/lib/navigation/app_router.dart index d9ef8b9..70b55a2 100644 --- a/frontend/lib/navigation/app_router.dart +++ b/frontend/lib/navigation/app_router.dart @@ -8,7 +8,7 @@ import '../screens/auth/parent_register_step3_screen.dart'; import '../screens/auth/parent_register_step4_screen.dart'; import '../screens/auth/parent_register_step5_screen.dart'; import '../screens/home/home_screen.dart'; -import '../models/placeholder_registration_data.dart'; +import '../models/user_registration_data.dart'; class AppRouter { static const String login = '/login'; @@ -23,6 +23,12 @@ class AppRouter { static Route generateRoute(RouteSettings settings) { Widget screen; bool slideTransition = false; + Object? args = settings.arguments; + + Widget buildErrorScreen(String step) { + print("Erreur: Données UserRegistrationData manquantes ou de mauvais type pour l'étape $step"); + return const ParentRegisterStep1Screen(); + } switch (settings.name) { case login: @@ -30,32 +36,43 @@ class AppRouter { break; case registerChoice: screen = const RegisterChoiceScreen(); - slideTransition = true; // Activer la transition pour cet écran + slideTransition = true; break; case parentRegisterStep1: screen = const ParentRegisterStep1Screen(); - slideTransition = true; // Activer la transition pour cet écran + slideTransition = true; break; case parentRegisterStep2: - screen = const ParentRegisterStep2Screen(); + if (args is UserRegistrationData) { + screen = ParentRegisterStep2Screen(registrationData: args); + } else { + screen = buildErrorScreen('2'); + } slideTransition = true; break; case parentRegisterStep3: - screen = const ParentRegisterStep3Screen(); + if (args is UserRegistrationData) { + screen = ParentRegisterStep3Screen(registrationData: args); + } else { + screen = buildErrorScreen('3'); + } slideTransition = true; break; case parentRegisterStep4: - screen = const ParentRegisterStep4Screen(); + if (args is UserRegistrationData) { + screen = ParentRegisterStep4Screen(registrationData: args); + } else { + screen = buildErrorScreen('4'); + } slideTransition = true; break; case parentRegisterStep5: - final args = settings.arguments as PlaceholderRegistrationData?; - if (args != null) { + if (args is UserRegistrationData) { screen = ParentRegisterStep5Screen(registrationData: args); } else { - print("Erreur: Données d'inscription manquantes pour l'étape 5"); - screen = const RegisterChoiceScreen(); + screen = buildErrorScreen('5'); } + slideTransition = true; break; case home: screen = const HomeScreen(); @@ -72,22 +89,16 @@ class AppRouter { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => screen, transitionsBuilder: (context, animation, secondaryAnimation, child) { - const begin = Offset(1.0, 0.0); // Glisse depuis la droite + const begin = Offset(1.0, 0.0); const end = Offset.zero; - const curve = Curves.easeInOut; // Animation douce - + const curve = Curves.easeInOut; var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); var offsetAnimation = animation.drive(tween); - - return SlideTransition( - position: offsetAnimation, - child: child, - ); + return SlideTransition(position: offsetAnimation, child: child); }, - transitionDuration: const Duration(milliseconds: 400), // Durée de la transition + transitionDuration: const Duration(milliseconds: 400), ); } else { - // Transition par défaut pour les autres écrans return MaterialPageRoute(builder: (_) => screen); } } diff --git a/frontend/lib/screens/auth/parent_register_step1_screen.dart b/frontend/lib/screens/auth/parent_register_step1_screen.dart index 4f9e6d8..0463967 100644 --- a/frontend/lib/screens/auth/parent_register_step1_screen.dart +++ b/frontend/lib/screens/auth/parent_register_step1_screen.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron +import '../../models/user_registration_data.dart'; // Import du modèle de données +import '../../utils/data_generator.dart'; // Import du générateur de données +import '../../widgets/custom_app_text_field.dart'; // Import du widget CustomAppTextField +import '../../models/card_assets.dart'; // Import des enums de cartes class ParentRegisterStep1Screen extends StatefulWidget { const ParentRegisterStep1Screen({super.key}); @@ -11,17 +15,42 @@ class ParentRegisterStep1Screen extends StatefulWidget { class _ParentRegisterStep1ScreenState extends State { final _formKey = GlobalKey(); + late UserRegistrationData _registrationData; - // Contrôleurs pour les champs + // Contrôleurs pour les champs (restauration CP et Ville) final _lastNameController = TextEditingController(); final _firstNameController = TextEditingController(); final _phoneController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); - final _addressController = TextEditingController(); - final _postalCodeController = TextEditingController(); - final _cityController = TextEditingController(); + final _addressController = TextEditingController(); // Rue seule + final _postalCodeController = TextEditingController(); // Restauré + final _cityController = TextEditingController(); // Restauré + + @override + void initState() { + super.initState(); + _registrationData = UserRegistrationData(); + _generateAndFillData(); + } + + void _generateAndFillData() { + final String genFirstName = DataGenerator.firstName(); + final String genLastName = DataGenerator.lastName(); + + // Utilisation des méthodes publiques de DataGenerator + _addressController.text = DataGenerator.address(); + _postalCodeController.text = DataGenerator.postalCode(); + _cityController.text = DataGenerator.city(); + + _firstNameController.text = genFirstName; + _lastNameController.text = genLastName; + _phoneController.text = DataGenerator.phone(); + _emailController.text = DataGenerator.email(genFirstName, genLastName); + _passwordController.text = DataGenerator.password(); + _confirmPasswordController.text = _passwordController.text; + } @override void dispose() { @@ -61,13 +90,13 @@ class _ParentRegisterStep1ScreenState extends State { children: [ // Indicateur d'étape (à rendre dynamique) Text( - 'Étape 1/X', + 'Étape 1/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), ), const SizedBox(height: 10), // Texte d'instruction Text( - 'Merci de renseigner les informations du premier parent :', + 'Informations du Parent Principal', style: GoogleFonts.merienda( fontSize: 24, fontWeight: FontWeight.bold, @@ -80,10 +109,11 @@ class _ParentRegisterStep1ScreenState extends State { // Carte jaune contenant le formulaire Container( width: screenSize.width * 0.6, - padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50), - decoration: const BoxDecoration( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 50), + constraints: const BoxConstraints(minHeight: 570), + decoration: BoxDecoration( image: DecorationImage( - image: AssetImage('assets/images/card_yellow_h.png'), + image: AssetImage(CardColorHorizontal.peach.path), fit: BoxFit.fill, ), ), @@ -94,35 +124,49 @@ class _ParentRegisterStep1ScreenState extends State { children: [ Row( children: [ - Expanded(child: _buildTextField(_lastNameController, 'Nom', hintText: 'Votre nom de famille')), - const SizedBox(width: 20), - Expanded(child: _buildTextField(_firstNameController, 'Prénom', hintText: 'Votre prénom')), + Expanded(flex: 12, child: CustomAppTextField(controller: _lastNameController, labelText: 'Nom', hintText: 'Votre nom de famille', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), + Expanded(flex: 1, child: const SizedBox()), // Espace de 4% + Expanded(flex: 12, child: CustomAppTextField(controller: _firstNameController, labelText: 'Prénom', hintText: 'Votre prénom', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), ], ), const SizedBox(height: 20), Row( children: [ - Expanded(child: _buildTextField(_phoneController, 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Votre numéro de téléphone')), - const SizedBox(width: 20), - Expanded(child: _buildTextField(_emailController, 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Votre adresse e-mail')), + Expanded(flex: 12, child: CustomAppTextField(controller: _phoneController, labelText: 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Votre numéro de téléphone', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), + Expanded(flex: 1, child: const SizedBox()), // Espace de 4% + Expanded(flex: 12, child: CustomAppTextField(controller: _emailController, labelText: 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Votre adresse e-mail', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), ], ), const SizedBox(height: 20), Row( children: [ - Expanded(child: _buildTextField(_passwordController, 'Mot de passe', obscureText: true, hintText: 'Créez votre mot de passe')), - const SizedBox(width: 20), - Expanded(child: _buildTextField(_confirmPasswordController, 'Confirmation', obscureText: true, hintText: 'Confirmez le mot de passe')), + Expanded(flex: 12, child: CustomAppTextField(controller: _passwordController, labelText: 'Mot de passe', obscureText: true, hintText: 'Créez votre mot de passe', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, validator: (value) { + if (value == null || value.isEmpty) return 'Mot de passe requis'; + if (value.length < 6) return '6 caractères minimum'; + return null; + })), + Expanded(flex: 1, child: const SizedBox()), // Espace de 4% + Expanded(flex: 12, child: CustomAppTextField(controller: _confirmPasswordController, labelText: 'Confirmation', obscureText: true, hintText: 'Confirmez le mot de passe', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, validator: (value) { + if (value == null || value.isEmpty) return 'Confirmation requise'; + if (value != _passwordController.text) return 'Ne correspond pas'; + return null; + })), ], ), const SizedBox(height: 20), - _buildTextField(_addressController, 'Adresse (Rue)', hintText: 'Numéro et nom de votre rue'), + CustomAppTextField( + controller: _addressController, + labelText: 'Adresse (N° et Rue)', + hintText: 'Numéro et nom de votre rue', + style: CustomAppTextFieldStyle.beige, + fieldWidth: double.infinity, + ), const SizedBox(height: 20), Row( children: [ - Expanded(flex: 2, child: _buildTextField(_postalCodeController, 'Code Postal', keyboardType: TextInputType.number, hintText: 'Code postal')), + Expanded(flex: 1, child: CustomAppTextField(controller: _postalCodeController, labelText: 'Code Postal', keyboardType: TextInputType.number, hintText: 'Code postal', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), const SizedBox(width: 20), - Expanded(flex: 3, child: _buildTextField(_cityController, 'Ville', hintText: 'Votre ville')), + Expanded(flex: 4, child: CustomAppTextField(controller: _cityController, labelText: 'Ville', hintText: 'Votre ville', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), ], ), ], @@ -157,8 +201,19 @@ class _ParentRegisterStep1ScreenState extends State { icon: Image.asset('assets/images/chevron_right.png', height: 40), onPressed: () { if (_formKey.currentState?.validate() ?? false) { - // TODO: Sauvegarder les données du parent 1 - Navigator.pushNamed(context, '/parent-register/step2'); // Naviguer vers l'étape 2 + _registrationData.updateParent1( + ParentData( + firstName: _firstNameController.text, + lastName: _lastNameController.text, + address: _addressController.text, // Rue + postalCode: _postalCodeController.text, // Ajout + city: _cityController.text, // Ajout + phone: _phoneController.text, + email: _emailController.text, + password: _passwordController.text, + ) + ); + Navigator.pushNamed(context, '/parent-register/step2', arguments: _registrationData); } }, tooltip: 'Suivant', @@ -168,59 +223,4 @@ class _ParentRegisterStep1ScreenState extends State { ), ); } - - // Widget pour construire les champs de texte avec le fond personnalisé - Widget _buildTextField( - TextEditingController controller, - String label, { - TextInputType? keyboardType, - bool obscureText = false, - String? hintText, - }) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '$label :', - style: GoogleFonts.merienda(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87), - ), - const SizedBox(height: 5), - Container( - height: 50, - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/images/input_field_bg.png'), - fit: BoxFit.fill, - ), - ), - child: TextFormField( - controller: controller, - keyboardType: keyboardType, - obscureText: obscureText, - style: GoogleFonts.merienda(fontSize: 16, color: Colors.black87), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14), - hintText: hintText ?? label, - hintStyle: GoogleFonts.merienda(fontSize: 16, color: Colors.black38), - ), - validator: (value) { - // Validation désactivée - return null; - /* - if (value == null || value.isEmpty) { - return 'Ce champ est obligatoire'; - } - // TODO: Ajouter des validations spécifiques (email, téléphone, mot de passe) - if (label == 'Confirmation' && value != _passwordController.text) { - return 'Les mots de passe ne correspondent pas'; - } - return null; - */ - }, - ), - ), - ], - ); - } } \ No newline at end of file diff --git a/frontend/lib/screens/auth/parent_register_step2_screen.dart b/frontend/lib/screens/auth/parent_register_step2_screen.dart index a8b44b8..a7ecaf2 100644 --- a/frontend/lib/screens/auth/parent_register_step2_screen.dart +++ b/frontend/lib/screens/auth/parent_register_step2_screen.dart @@ -1,9 +1,15 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron +import '../../models/user_registration_data.dart'; // Import du modèle +import '../../utils/data_generator.dart'; // Import du générateur +import '../../widgets/custom_app_text_field.dart'; // Import du widget +import '../../models/card_assets.dart'; // Import des enums de cartes class ParentRegisterStep2Screen extends StatefulWidget { - const ParentRegisterStep2Screen({super.key}); + final UserRegistrationData registrationData; // Accepte les données de l'étape 1 + + const ParentRegisterStep2Screen({super.key, required this.registrationData}); @override State createState() => _ParentRegisterStep2ScreenState(); @@ -11,25 +17,54 @@ class ParentRegisterStep2Screen extends StatefulWidget { class _ParentRegisterStep2ScreenState extends State { final _formKey = GlobalKey(); + late UserRegistrationData _registrationData; // Copie locale pour modification - // TODO: Recevoir les infos du parent 1 pour pré-remplir l'adresse - // String? _parent1Address; - // String? _parent1PostalCode; - // String? _parent1City; + bool _addParent2 = true; // Pour le test, on ajoute toujours le parent 2 + bool _sameAddressAsParent1 = false; // Peut être généré aléatoirement aussi - bool _addParent2 = false; // Par défaut, on n'ajoute pas le parent 2 - bool _sameAddressAsParent1 = false; - - // Contrôleurs pour les champs du parent 2 + // Contrôleurs pour les champs du parent 2 (restauration CP et Ville) final _lastNameController = TextEditingController(); final _firstNameController = TextEditingController(); final _phoneController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); - final _addressController = TextEditingController(); - final _postalCodeController = TextEditingController(); - final _cityController = TextEditingController(); + final _addressController = TextEditingController(); // Rue seule + final _postalCodeController = TextEditingController(); // Restauré + final _cityController = TextEditingController(); // Restauré + + @override + void initState() { + super.initState(); + _registrationData = widget.registrationData; // Récupère les données de l'étape 1 + if (_addParent2) { + _generateAndFillParent2Data(); + } + } + + void _generateAndFillParent2Data() { + final String genFirstName = DataGenerator.firstName(); + final String genLastName = DataGenerator.lastName(); + _firstNameController.text = genFirstName; + _lastNameController.text = genLastName; + _phoneController.text = DataGenerator.phone(); + _emailController.text = DataGenerator.email(genFirstName, genLastName); + _passwordController.text = DataGenerator.password(); + _confirmPasswordController.text = _passwordController.text; + + _sameAddressAsParent1 = DataGenerator.boolean(); + if (!_sameAddressAsParent1) { + // Générer adresse, CP, Ville séparément + _addressController.text = DataGenerator.address(); + _postalCodeController.text = DataGenerator.postalCode(); + _cityController.text = DataGenerator.city(); + } else { + // Vider les champs si même adresse (seront désactivés) + _addressController.clear(); + _postalCodeController.clear(); + _cityController.clear(); + } + } @override void dispose() { @@ -44,10 +79,8 @@ class _ParentRegisterStep2ScreenState extends State { _cityController.dispose(); super.dispose(); } - - // Helper pour activer/désactiver tous les champs sauf l'adresse + bool get _parent2FieldsEnabled => _addParent2; - // Helper pour activer/désactiver les champs d'adresse bool get _addressFieldsEnabled => _addParent2 && !_sameAddressAsParent1; @override @@ -57,174 +90,104 @@ class _ParentRegisterStep2ScreenState extends State { return Scaffold( body: Stack( children: [ - // Fond papier Positioned.fill( - child: Image.asset( - 'assets/images/paper2.png', - fit: BoxFit.cover, - repeat: ImageRepeat.repeat, - ), + child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat), ), - - // Contenu centré Center( child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // Indicateur d'étape - Text( - 'Étape 2/X', // Mettre à jour le numéro d'étape total - style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), - ), + Text('Étape 2/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)), const SizedBox(height: 10), - // Texte d'instruction Text( - 'Renseignez les informations du deuxième parent (optionnel) :', - style: GoogleFonts.merienda( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), + 'Informations du Deuxième Parent (Optionnel)', + style: GoogleFonts.merienda(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87), textAlign: TextAlign.center, ), const SizedBox(height: 30), - - // Carte bleue contenant le formulaire Container( width: screenSize.width * 0.6, padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50), - decoration: const BoxDecoration( // Retour à la décoration - image: DecorationImage( - image: AssetImage('assets/images/card_blue_h.png'), // Utilisation de l'image horizontale - fit: BoxFit.fill, - ), + decoration: BoxDecoration( + image: DecorationImage(image: AssetImage(CardColorHorizontal.blue.path), fit: BoxFit.fill), ), - // Suppression du Stack et Transform.rotate child: Form( key: _formKey, - child: SingleChildScrollView( // Le SingleChildScrollView redevient l'enfant direct + child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ - // --- Interrupteurs sur une ligne --- Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Option 1: Ajouter Parent 2 - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.person_add_alt_1, size: 20), - const SizedBox(width: 8), - Flexible( - child: Text( - 'Ajouter Parent 2 ?', - style: GoogleFonts.merienda(fontWeight: FontWeight.bold), - overflow: TextOverflow.ellipsis, - ), - ), - _buildCustomSwitch( - value: _addParent2, - onChanged: (bool? newValue) { - final bool actualValue = newValue ?? false; - setState(() { - _addParent2 = actualValue; - if (!_addParent2) { - _formKey.currentState?.reset(); - _lastNameController.clear(); - _firstNameController.clear(); - _phoneController.clear(); - _emailController.clear(); - _passwordController.clear(); - _confirmPasswordController.clear(); - _addressController.clear(); - _postalCodeController.clear(); - _cityController.clear(); - _sameAddressAsParent1 = false; - } - }); - }, - ), - ], - ), - ), - const SizedBox(width: 10), - - // Option 2: Même Adresse - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.home_work_outlined, size: 20, color: _addParent2 ? null : Colors.grey), // Griser l'icône si désactivé - const SizedBox(width: 8), - Flexible( - child: Text( - 'Même Adresse ?', - style: GoogleFonts.merienda(color: _addParent2 ? null : Colors.grey), // Griser le texte si désactivé - overflow: TextOverflow.ellipsis, - ), - ), - _buildCustomSwitch( - value: _sameAddressAsParent1, - onChanged: _addParent2 ? (bool? newValue) { - final bool actualValue = newValue ?? false; - setState(() { - _sameAddressAsParent1 = actualValue; - if (_sameAddressAsParent1) { - _addressController.clear(); - _postalCodeController.clear(); - _cityController.clear(); - // TODO: Pré-remplir - } - }); - } : null, - ), - ], - ), - ), - ], - ), - const SizedBox(height: 25), // Espacement ajusté après les switchs - - // --- Champs du Parent 2 (conditionnels) --- - // Nom & Prénom + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 12, + child: Row(children: [ + const Icon(Icons.person_add_alt_1, size: 20), const SizedBox(width: 8), + Flexible(child: Text('Ajouter Parent 2 ?', style: GoogleFonts.merienda(fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis)), + const Spacer(), + Switch(value: _addParent2, onChanged: (val) => setState(() { + _addParent2 = val ?? false; + if (_addParent2) _generateAndFillParent2Data(); else _clearParent2Fields(); + }), activeColor: Theme.of(context).primaryColor), + ]), + ), + Expanded(flex: 1, child: const SizedBox()), + Expanded( + flex: 12, + child: Row(children: [ + Icon(Icons.home_work_outlined, size: 20, color: _addParent2 ? null : Colors.grey), + const SizedBox(width: 8), + Flexible(child: Text('Même Adresse ?', style: GoogleFonts.merienda(color: _addParent2 ? null : Colors.grey), overflow: TextOverflow.ellipsis)), + const Spacer(), + Switch(value: _sameAddressAsParent1, onChanged: _addParent2 ? (val) => setState(() { + _sameAddressAsParent1 = val ?? false; + if (_sameAddressAsParent1) { + _addressController.text = _registrationData.parent1.address; + _postalCodeController.text = _registrationData.parent1.postalCode; + _cityController.text = _registrationData.parent1.city; + } else { + _addressController.text = DataGenerator.address(); + _postalCodeController.text = DataGenerator.postalCode(); + _cityController.text = DataGenerator.city(); + } + }) : null, activeColor: Theme.of(context).primaryColor), + ]), + ), + ]), + const SizedBox(height: 25), Row( children: [ - Expanded(child: _buildTextField(_lastNameController, 'Nom', hintText: 'Nom du deuxième parent', enabled: _parent2FieldsEnabled)), - const SizedBox(width: 20), - Expanded(child: _buildTextField(_firstNameController, 'Prénom', hintText: 'Prénom du deuxième parent', enabled: _parent2FieldsEnabled)), + Expanded(flex: 12, child: CustomAppTextField(controller: _lastNameController, labelText: 'Nom', hintText: 'Nom du parent 2', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), + Expanded(flex: 1, child: const SizedBox()), // Espace de 4% + Expanded(flex: 12, child: CustomAppTextField(controller: _firstNameController, labelText: 'Prénom', hintText: 'Prénom du parent 2', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), ], ), const SizedBox(height: 20), - // Téléphone & Email Row( children: [ - Expanded(child: _buildTextField(_phoneController, 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Son numéro de téléphone', enabled: _parent2FieldsEnabled)), - const SizedBox(width: 20), - Expanded(child: _buildTextField(_emailController, 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Son adresse e-mail', enabled: _parent2FieldsEnabled)), + Expanded(flex: 12, child: CustomAppTextField(controller: _phoneController, labelText: 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Son téléphone', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), + Expanded(flex: 1, child: const SizedBox()), // Espace de 4% + Expanded(flex: 12, child: CustomAppTextField(controller: _emailController, labelText: 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Son email', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), ], ), const SizedBox(height: 20), - // Mot de passe Row( children: [ - Expanded(child: _buildTextField(_passwordController, 'Mot de passe', obscureText: true, hintText: 'Son mot de passe', enabled: _parent2FieldsEnabled)), - const SizedBox(width: 20), - Expanded(child: _buildTextField(_confirmPasswordController, 'Confirmation', obscureText: true, hintText: 'Confirmer son mot de passe', enabled: _parent2FieldsEnabled)), + Expanded(flex: 12, child: CustomAppTextField(controller: _passwordController, labelText: 'Mot de passe', obscureText: true, hintText: 'Son mot de passe', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, validator: _addParent2 ? (v) => (v == null || v.isEmpty ? 'Requis' : (v.length < 6 ? '6 car. min' : null)) : null)), + Expanded(flex: 1, child: const SizedBox()), // Espace de 4% + Expanded(flex: 12, child: CustomAppTextField(controller: _confirmPasswordController, labelText: 'Confirmation', obscureText: true, hintText: 'Confirmer mot de passe', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, validator: _addParent2 ? (v) => (v == null || v.isEmpty ? 'Requis' : (v != _passwordController.text ? 'Différent' : null)) : null)), ], ), const SizedBox(height: 20), - - // --- Champs Adresse (conditionnels) --- - _buildTextField(_addressController, 'Adresse (Rue)', hintText: 'Son numéro et nom de rue', enabled: _addressFieldsEnabled), + CustomAppTextField(controller: _addressController, labelText: 'Adresse (N° et Rue)', hintText: 'Son numéro et nom de rue', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity), const SizedBox(height: 20), Row( children: [ - Expanded(flex: 2, child: _buildTextField(_postalCodeController, 'Code Postal', keyboardType: TextInputType.number, hintText: 'Son code postal', enabled: _addressFieldsEnabled)), + Expanded(flex: 1, child: CustomAppTextField(controller: _postalCodeController, labelText: 'Code Postal', keyboardType: TextInputType.number, hintText: 'Son code postal', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), const SizedBox(width: 20), - Expanded(flex: 3, child: _buildTextField(_cityController, 'Ville', hintText: 'Sa ville', enabled: _addressFieldsEnabled)), + Expanded(flex: 4, child: CustomAppTextField(controller: _cityController, labelText: 'Ville', hintText: 'Sa ville', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)), ], ), ], @@ -236,50 +199,39 @@ class _ParentRegisterStep2ScreenState extends State { ), ), ), - - // Chevron de navigation gauche (Retour) 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), // Retour à l'étape 1 + 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', ), ), - - // Chevron de navigation droit (Suivant) Positioned( top: screenSize.height / 2 - 20, right: 40, child: IconButton( icon: Image.asset('assets/images/chevron_right.png', height: 40), onPressed: () { - // Si on n'ajoute pas de parent 2, on passe directement - if (!_addParent2) { - // Naviguer vers l'étape 3 (enfants) - print('Passer à l\'étape 3 (enfants) - Sans Parent 2'); - Navigator.pushNamed(context, '/parent-register/step3'); - return; - } - // Si on ajoute un parent 2 - // Valider seulement si on n'utilise PAS la même adresse - bool isFormValid = true; - // TODO: Remettre la validation quand elle sera prête - /* - if (!_sameAddressAsParent1) { - isFormValid = _formKey.currentState?.validate() ?? false; - } - */ - - if (isFormValid) { - // TODO: Sauvegarder les données du parent 2 - print('Passer à l\'étape 3 (enfants) - Avec Parent 2'); - Navigator.pushNamed(context, '/parent-register/step3'); + if (!_addParent2 || (_formKey.currentState?.validate() ?? false)) { + if (_addParent2) { + _registrationData.updateParent2( + ParentData( + firstName: _firstNameController.text, + lastName: _lastNameController.text, + address: _sameAddressAsParent1 ? _registrationData.parent1.address : _addressController.text, + postalCode: _sameAddressAsParent1 ? _registrationData.parent1.postalCode : _postalCodeController.text, + city: _sameAddressAsParent1 ? _registrationData.parent1.city : _cityController.text, + phone: _phoneController.text, + email: _emailController.text, + password: _passwordController.text, + ) + ); + } else { + _registrationData.updateParent2(null); + } + Navigator.pushNamed(context, '/parent-register/step3', arguments: _registrationData); } }, tooltip: 'Suivant', @@ -290,85 +242,14 @@ class _ParentRegisterStep2ScreenState extends State { ); } - // --- NOUVEAU WIDGET --- - // Widget pour construire un switch personnalisé avec images - Widget _buildCustomSwitch({required bool value, required ValueChanged? onChanged}) { - // --- DEBUG --- - print("Building Custom Switch with value: $value"); - // ------------- - const double switchHeight = 25.0; - const double switchWidth = 40.0; - - return InkWell( - onTap: onChanged != null ? () => onChanged(!value) : null, - child: Opacity( - // Griser le switch si désactivé - opacity: onChanged != null ? 1.0 : 0.5, - child: Image.asset( - value ? 'assets/images/switch_on.png' : 'assets/images/switch_off.png', - height: switchHeight, - width: switchWidth, - fit: BoxFit.contain, // Ou BoxFit.fill selon le rendu souhaité - ), - ), - ); - } - - // Widget pour construire les champs de texte (identique à l'étape 1) - Widget _buildTextField( - TextEditingController controller, - String label, { - TextInputType? keyboardType, - bool obscureText = false, - String? hintText, - bool enabled = true, - }) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '$label :', - style: GoogleFonts.merienda(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87), - ), - const SizedBox(height: 5), - Container( - height: 50, - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/images/input_field_bg.png'), - fit: BoxFit.fill, - ), - ), - child: TextFormField( - controller: controller, - keyboardType: keyboardType, - obscureText: obscureText, - enabled: enabled, - style: GoogleFonts.merienda(fontSize: 16, color: enabled ? Colors.black87 : Colors.grey), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14), - hintText: hintText ?? label, - hintStyle: GoogleFonts.merienda(fontSize: 16, color: Colors.black38), - ), - validator: (value) { - if (!enabled) return null; // Ne pas valider si désactivé - // Le reste de la validation (commentée précédemment) - return null; - /* - if (value == null || value.isEmpty) { - return 'Ce champ est obligatoire'; - } - // TODO: Validations spécifiques - if (label == 'Confirmation' && value != _passwordController.text) { - return 'Les mots de passe ne correspondent pas'; - } - return null; - */ - }, - ), - ), - ], - ); + void _clearParent2Fields() { + _formKey.currentState?.reset(); + _lastNameController.clear(); _firstNameController.clear(); _phoneController.clear(); + _emailController.clear(); _passwordController.clear(); _confirmPasswordController.clear(); + _addressController.clear(); + _postalCodeController.clear(); + _cityController.clear(); + _sameAddressAsParent1 = false; + setState(() {}); } } \ No newline at end of file diff --git a/frontend/lib/screens/auth/parent_register_step3_screen.dart b/frontend/lib/screens/auth/parent_register_step3_screen.dart index f696b2f..dce2488 100644 --- a/frontend/lib/screens/auth/parent_register_step3_screen.dart +++ b/frontend/lib/screens/auth/parent_register_step3_screen.dart @@ -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 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 + 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 _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> _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 { } 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(); }); - // 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 _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 _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 parts = currentChild.dobController.text.split('/'); + 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) { /* Ignorer */ } + } catch (e) {} } } else { - if (currentChild.dobController.text.isNotEmpty) { + if (currentChild.dob.isNotEmpty) { try { - List parts = currentChild.dobController.text.split('/'); + 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) { /* 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 { 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), - ), - ); - } - }, - ), + 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), + 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 { 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 { } } -// 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 onFirstNameChanged; + final ValueChanged onLastNameChanged; 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 + 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), ), ), diff --git a/frontend/lib/screens/auth/parent_register_step4_screen.dart b/frontend/lib/screens/auth/parent_register_step4_screen.dart index 37fa46d..fca0d10 100644 --- a/frontend/lib/screens/auth/parent_register_step4_screen.dart +++ b/frontend/lib/screens/auth/parent_register_step4_screen.dart @@ -3,18 +3,30 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:p_tits_pas/widgets/custom_decorated_text_field.dart'; // Import du nouveau widget import 'dart:math' as math; // Pour la rotation du chevron import 'package:p_tits_pas/widgets/app_custom_checkbox.dart'; // Import de la checkbox personnalisée -import 'package:p_tits_pas/models/placeholder_registration_data.dart'; +// import 'package:p_tits_pas/models/placeholder_registration_data.dart'; // Remplacé +import '../../models/user_registration_data.dart'; // Import du vrai modèle +import '../../utils/data_generator.dart'; // Import du générateur class ParentRegisterStep4Screen extends StatefulWidget { - const ParentRegisterStep4Screen({super.key}); + final UserRegistrationData registrationData; // Accepte les données + + const ParentRegisterStep4Screen({super.key, required this.registrationData}); @override State createState() => _ParentRegisterStep4ScreenState(); } class _ParentRegisterStep4ScreenState extends State { + late UserRegistrationData _registrationData; // État local final _motivationController = TextEditingController(); - bool _cguAccepted = false; + bool _cguAccepted = true; // Pour le test, CGU acceptées par défaut + + @override + void initState() { + super.initState(); + _registrationData = widget.registrationData; + _motivationController.text = DataGenerator.motivation(); // Générer la motivation + } @override void dispose() { @@ -113,7 +125,7 @@ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lo ), const SizedBox(height: 20), Text( - 'Merci de motiver votre demande création de compte :', + 'Motivation de votre demande', style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), textAlign: TextAlign.center, ), @@ -132,35 +144,23 @@ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lo const SizedBox(height: 30), GestureDetector( onTap: () { - // Si on clique sur la zone et que ce n'est pas encore accepté, - // ou si c'est déjà accepté (l'utilisateur veut peut-être revoir les CGU) - _showCGUModal(); + if (!_cguAccepted) { + _showCGUModal(); + } }, child: AppCustomCheckbox( label: 'J\'accepte les conditions générales d\'utilisation', value: _cguAccepted, onChanged: (newValue) { - // La logique d'ouverture de la modale est déclenchée par le GestureDetector externe. - // La modale mettra à jour _cguAccepted et reconstruira le widget. - // Si newValue est true (ce qui signifie que la modale a été acceptée et _cguAccepted mis à jour), - // on n'a rien à faire de plus ici. - // Si newValue est false (l'utilisateur a réussi à la décocher d'une manière ou d'une autre, - // ce qui ne devrait pas arriver car on ouvre toujours la modale), - // on pourrait vouloir forcer l'affichage de la modale aussi. - // Pour l'instant, on se fie au fait que la modale gère l'acceptation. - if (!_cguAccepted) { // Si pas encore accepté, la modale doit s'ouvrir - _showCGUModal(); - } else if (newValue == false) { // Si on essaie de décocher - // Optionnel: Forcer la non-acceptation et rouvrir la modale ? - // Pour l'instant, on ne fait rien, la modale gère. + if (!_cguAccepted) { + _showCGUModal(); + } else { + setState(() => _cguAccepted = false); } }, - // Vous pouvez ajuster checkboxSize et checkmarkSizeFactor si nécessaire ), ), const SizedBox(height: 40), - // On ajoutera un Form et un bouton de soumission principal plus tard - // Pour l'instant, les chevrons servent à la navigation simple ], ), ), @@ -182,19 +182,16 @@ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lo icon: Image.asset('assets/images/chevron_right.png', height: 40), onPressed: _cguAccepted ? () { - print('Motivation: ${_motivationController.text}'); - print('CGU acceptées: $_cguAccepted'); - - // TODO: Rassembler toutes les données des étapes précédentes - final dummyData = PlaceholderRegistrationData(parent1Name: "Parent 1 Nom (Exemple)"); + _registrationData.updateMotivation(_motivationController.text); + _registrationData.acceptCGU(); Navigator.pushNamed( context, '/parent-register/step5', - arguments: dummyData // Passer les données (ici factices) + arguments: _registrationData ); } - : null, // Désactiver si les CGU ne sont pas acceptées + : null, tooltip: 'Suivant', ), ), diff --git a/frontend/lib/screens/auth/parent_register_step5_screen.dart b/frontend/lib/screens/auth/parent_register_step5_screen.dart index e030f9a..05101fd 100644 --- a/frontend/lib/screens/auth/parent_register_step5_screen.dart +++ b/frontend/lib/screens/auth/parent_register_step5_screen.dart @@ -1,18 +1,192 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import '../../models/placeholder_registration_data.dart'; // Assurez-vous que le chemin est correct +import '../../models/user_registration_data.dart'; // Utilisation du vrai modèle import '../../widgets/image_button.dart'; // Import du ImageButton +import '../../models/card_assets.dart'; // Import des enums de cartes -// La définition locale de PlaceholderRegistrationData est supprimée ici. +// Nouvelle méthode helper pour afficher un champ de type "lecture seule" stylisé +Widget _buildDisplayFieldValue(BuildContext context, String label, String value, {bool multiLine = false, double fieldHeight = 50.0}) { + const double detailFontSize = 18.0; + const FontWeight labelFontWeight = FontWeight.w600; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: GoogleFonts.merienda(fontSize: detailFontSize, fontWeight: labelFontWeight)), + const SizedBox(height: 4), + Container( + width: double.infinity, // Prendra la largeur allouée par son parent (Expanded) + height: multiLine ? null : fieldHeight, // Hauteur flexible pour multiligne, sinon fixe + constraints: multiLine ? const BoxConstraints(minHeight: 50.0) : null, // Hauteur min pour multiligne + padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), // Ajuster au besoin + decoration: BoxDecoration( + image: const DecorationImage( + image: AssetImage('assets/images/input_field_bg.png'), // Image de fond du champ + fit: BoxFit.fill, + ), + // Si votre image input_field_bg.png a des coins arrondis intrinsèques, ce borderRadius n'est pas nécessaire + // ou doit correspondre. Sinon, pour une image rectangulaire, vous pouvez l'ajouter. + // borderRadius: BorderRadius.circular(12), + ), + child: Text( + value.isNotEmpty ? value : '-', + style: GoogleFonts.merienda(fontSize: detailFontSize), + maxLines: multiLine ? null : 1, // Permet plusieurs lignes si multiLine est true + overflow: multiLine ? TextOverflow.visible : TextOverflow.ellipsis, + ), + ), + ], + ); +} class ParentRegisterStep5Screen extends StatelessWidget { - final PlaceholderRegistrationData registrationData; // Doit maintenant utiliser la version importée + final UserRegistrationData registrationData; const ParentRegisterStep5Screen({super.key, required this.registrationData}); + // Méthode pour construire la carte Parent 1 + Widget _buildParent1Card(BuildContext context, ParentData data) { + const double verticalSpacing = 15.0; // Espacement vertical entre les "champs" + + List details = [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Nom:", data.lastName)), + const SizedBox(width: 20), // Espace entre les champs dans la Row + Expanded(child: _buildDisplayFieldValue(context, "Prénom:", data.firstName)), + ], + ), + const SizedBox(height: verticalSpacing), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Téléphone:", data.phone)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Email:", data.email, multiLine: true)), // Email peut nécessiter plusieurs lignes + ], + ), + const SizedBox(height: verticalSpacing), + _buildDisplayFieldValue(context, "Adresse:", "${data.address}\n${data.postalCode} ${data.city}".trim(), multiLine: true, fieldHeight: 80), // fieldHeight est une suggestion pour 2 lignes + ]; + return _SummaryCard( + backgroundImagePath: CardColorHorizontal.peach.path, + title: 'Parent Principal', + content: details, + onEdit: () => Navigator.of(context).pushNamed('/parent-register/step1', arguments: registrationData), + ); + } + + // Méthode pour construire la carte Parent 2 + Widget _buildParent2Card(BuildContext context, ParentData data) { + const double verticalSpacing = 15.0; + // Structure similaire à _buildParent1Card + List details = [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Nom:", data.lastName)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Prénom:", data.firstName)), + ], + ), + const SizedBox(height: verticalSpacing), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Téléphone:", data.phone)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Email:", data.email, multiLine: true)), + ], + ), + const SizedBox(height: verticalSpacing), + _buildDisplayFieldValue(context, "Adresse:", "${data.address}\n${data.postalCode} ${data.city}".trim(), multiLine: true, fieldHeight: 80), + ]; + return _SummaryCard( + backgroundImagePath: CardColorHorizontal.blue.path, + title: 'Deuxième Parent', + content: details, + onEdit: () => Navigator.of(context).pushNamed('/parent-register/step2', arguments: registrationData), + ); + } + + // Méthode pour construire les cartes Enfants + List _buildChildrenCards(BuildContext context, List children) { + return children.asMap().entries.map((entry) { + int index = entry.key; + ChildData child = entry.value; + + // Convertir CardColorVertical en CardColorHorizontal pour le récapitulatif + // Ceci suppose que les noms de couleurs correspondent entre les deux enums. + CardColorHorizontal cardColorHorizontal = CardColorHorizontal.values.firstWhere( + (e) => e.name == child.cardColor.name, + orElse: () => CardColorHorizontal.lavender, // Couleur par défaut si non trouvée + ); + + List details = [ + _buildDetailRow('Prénom', child.firstName), + _buildDetailRow('Nom', child.lastName), + _buildDetailRow(child.isUnbornChild ? 'Date Prév.' : 'Naissance', child.dob), + _buildDetailRow('Cons. Photo', child.photoConsent ? 'Oui' : 'Non'), + _buildDetailRow('Naiss. Mult.', child.multipleBirth ? 'Oui' : 'Non'), + ]; + return Padding( + padding: const EdgeInsets.only(bottom: 20.0), // Espace entre les cartes enfants + child: _SummaryCard( + backgroundImagePath: cardColorHorizontal.path, // Utiliser la couleur convertie + title: 'Enfant ${index + 1}' + (child.isUnbornChild ? ' (à naître)' : ''), + content: details, + onEdit: () { /* TODO: Naviguer vers step3 et focus l'enfant index */ }, + ), + ); + }).toList(); + } + + // Méthode pour construire la carte Motivation + Widget _buildMotivationCard(BuildContext context, String motivation) { + List details = [ + Text(motivation.isNotEmpty ? motivation : 'Aucune motivation renseignée.', + style: GoogleFonts.merienda(fontSize: 18), + maxLines: 4, + overflow: TextOverflow.ellipsis) + ]; + return _SummaryCard( + backgroundImagePath: CardColorHorizontal.pink.path, + title: 'Votre Motivation', + content: details, + onEdit: () => Navigator.of(context).pushNamed('/parent-register/step4', arguments: registrationData), + ); + } + + // Helper pour afficher une ligne de détail (police et agencement amélioré) + Widget _buildDetailRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "$label: ", + style: GoogleFonts.merienda(fontSize: 18, fontWeight: FontWeight.w600), + ), + Expanded( + child: Text( + value.isNotEmpty ? value : '-', + style: GoogleFonts.merienda(fontSize: 18), + softWrap: true, + ), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; + final cardWidth = screenSize.width / 2.0; // Largeur de la carte (50% de l'écran) + final double imageAspectRatio = 2.0; // Ratio corrigé (1024/512 = 2.0) + final cardHeight = cardWidth / imageAspectRatio; return Scaffold( body: Stack( @@ -22,55 +196,49 @@ class ParentRegisterStep5Screen extends StatelessWidget { ), Center( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 50.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - 'Étape 5/5', - style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), - ), - const SizedBox(height: 20), - Text( - 'Récapitulatif de votre demande', - style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), - textAlign: TextAlign.center, - ), - const SizedBox(height: 30), - - // TODO: Construire les cartes récapitulatives ici - // _buildParent1Card(context, registrationData), - // if (registrationData.parent2Exists) _buildParent2Card(context, registrationData), - // ..._buildChildrenCards(context, registrationData), - // _buildMotivationCard(context, registrationData), - - const SizedBox(height: 40), - // Utilisation du ImageButton - ImageButton( - bg: 'assets/images/btn_green.png', - text: 'Soumettre ma demande', - textColor: const Color(0xFF2D6A4F), - width: 350, - height: 50, - fontSize: 18, - onPressed: () { - _showConfirmationModal(context); - }, - ), - ], + padding: const EdgeInsets.symmetric(vertical: 40.0), // Padding horizontal supprimé ici + child: Padding( // Ajout du Padding horizontal externe + padding: EdgeInsets.symmetric(horizontal: screenSize.width / 4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Étape 5/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)), + const SizedBox(height: 20), + Text('Récapitulatif de votre demande', style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), textAlign: TextAlign.center), + const SizedBox(height: 30), + + _buildParent1Card(context, registrationData.parent1), + const SizedBox(height: 20), + if (registrationData.parent2 != null) ...[ + _buildParent2Card(context, registrationData.parent2!), + const SizedBox(height: 20), + ], + ..._buildChildrenCards(context, registrationData.children), + _buildMotivationCard(context, registrationData.motivationText), + const SizedBox(height: 40), + ImageButton( + bg: 'assets/images/btn_green.png', + text: 'Soumettre ma demande', + textColor: const Color(0xFF2D6A4F), + width: 350, + height: 50, + fontSize: 18, + onPressed: () { + print("Données finales: ${registrationData.parent1.firstName}, Enfant(s): ${registrationData.children.length}"); + _showConfirmationModal(context); + }, + ), + ], + ), ), ), ), - // Chevrons de navigation (uniquement retour vers étape 4) Positioned( top: screenSize.height / 2 - 20, left: 40, child: IconButton( - icon: Transform.flip( - flipX: true, - child: Image.asset('assets/images/chevron_right.png', height: 40) - ), + icon: Transform.flip(flipX: true, child: Image.asset('assets/images/chevron_right.png', height: 40)), onPressed: () => Navigator.pop(context), // Retour à l'étape 4 tooltip: 'Retour', ), @@ -108,10 +276,64 @@ class ParentRegisterStep5Screen extends StatelessWidget { }, ); } +} - // TODO: Méthodes pour construire les cartes individuelles - // Widget _buildParent1Card(BuildContext context, PlaceholderRegistrationData data) { ... } - // Widget _buildParent2Card(BuildContext context, PlaceholderRegistrationData data) { ... } - // List _buildChildrenCards(BuildContext context, PlaceholderRegistrationData data) { ... } - // Widget _buildMotivationCard(BuildContext context, PlaceholderRegistrationData data) { ... } +// Widget générique _SummaryCard (ajusté) +class _SummaryCard extends StatelessWidget { + final String backgroundImagePath; + final String title; + final List content; + final VoidCallback onEdit; + + const _SummaryCard({ + super.key, + required this.backgroundImagePath, + required this.title, + required this.content, + required this.onEdit, + }); + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 2.0, // Le ratio largeur/hauteur de nos images de fond + child: Container( + padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 25.0), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(backgroundImagePath), + fit: BoxFit.cover, + ), + borderRadius: BorderRadius.circular(15), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, // Pour que la colonne prenne la hauteur du contenu + children: [ + Align( // Centrer le titre + alignment: Alignment.center, + child: Text( + title, + style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), // Police légèrement augmentée + ), + ), + const SizedBox(height: 12), // Espacement ajusté après le titre + ...content, + ], + ), + ), + IconButton( + icon: const Icon(Icons.edit, color: Colors.black54, size: 28), // Icône un peu plus grande + onPressed: onEdit, + tooltip: 'Modifier', + ), + ], + ), + ), + ); + } } \ No newline at end of file diff --git a/frontend/lib/screens/auth/register_choice_screen.dart b/frontend/lib/screens/auth/register_choice_screen.dart index 7e7d770..0b6bd8a 100644 --- a/frontend/lib/screens/auth/register_choice_screen.dart +++ b/frontend/lib/screens/auth/register_choice_screen.dart @@ -2,6 +2,7 @@ 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 widget générique +import '../../models/card_assets.dart'; // Import des enums de cartes class RegisterChoiceScreen extends StatelessWidget { const RegisterChoiceScreen({super.key}); @@ -73,9 +74,9 @@ class RegisterChoiceScreen extends StatelessWidget { aspectRatio: 2 / 3, child: Container( padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), - decoration: const BoxDecoration( + decoration: BoxDecoration( image: DecorationImage( - image: AssetImage('assets/images/card_rose.png'), + image: AssetImage(CardColorVertical.pink.path), fit: BoxFit.fill, ), ), diff --git a/frontend/lib/utils/data_generator.dart b/frontend/lib/utils/data_generator.dart new file mode 100644 index 0000000..a6f9077 --- /dev/null +++ b/frontend/lib/utils/data_generator.dart @@ -0,0 +1,65 @@ +import 'dart:math'; + +class DataGenerator { + static final Random _random = Random(); + + static final List _firstNames = [ + 'Alice', 'Bob', 'Charlie', 'David', 'Eva', 'Félix', 'Gabrielle', 'Hugo', 'Inès', 'Jules', + 'Léa', 'Manon', 'Nathan', 'Oscar', 'Pauline', 'Quentin', 'Raphaël', 'Sophie', 'Théo', 'Victoire' + ]; + + static final List _lastNames = [ + 'Martin', 'Bernard', 'Dubois', 'Thomas', 'Robert', 'Richard', 'Petit', 'Durand', 'Leroy', 'Moreau', + 'Simon', 'Laurent', 'Lefebvre', 'Michel', 'Garcia', 'David', 'Bertrand', 'Roux', 'Vincent', 'Fournier' + ]; + + static final List _addressSuffixes = [ + 'Rue de la Paix', 'Boulevard des Rêves', 'Avenue du Soleil', 'Place des Étoiles', 'Chemin des Champs' + ]; + + static final List _motivationSnippets = [ + 'Nous cherchons une personne de confiance.', + 'Nos horaires sont atypiques.', + 'Notre enfant est plein de vie.', + 'Nous souhaitons une garde à temps plein.', + 'Une adaptation en douceur est primordiale pour nous.', + 'Nous avons hâte de vous rencontrer.', + 'La pédagogie Montessori nous intéresse.' + ]; + + static String firstName() => _firstNames[_random.nextInt(_firstNames.length)]; + static String lastName() => _lastNames[_random.nextInt(_lastNames.length)]; + static String address() => "${_random.nextInt(100) + 1} ${_addressSuffixes[_random.nextInt(_addressSuffixes.length)]}"; + static String postalCode() => "750${_random.nextInt(10)}${_random.nextInt(10)}"; + static String city() => "Paris"; + static String phone() => "06${_random.nextInt(10)}${_random.nextInt(10)}${_random.nextInt(10)}${_random.nextInt(10)}${_random.nextInt(10)}${_random.nextInt(10)}${_random.nextInt(10)}${_random.nextInt(10)}"; + static String email(String firstName, String lastName) => "${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com"; + static String password() => "password123"; // Simple pour le test + + static String dob({bool isUnborn = false}) { + final now = DateTime.now(); + if (isUnborn) { + final provisionalDate = now.add(Duration(days: _random.nextInt(180) + 30)); // Entre 1 et 7 mois dans le futur + return "${provisionalDate.day.toString().padLeft(2, '0')}/${provisionalDate.month.toString().padLeft(2, '0')}/${provisionalDate.year}"; + } else { + final birthYear = now.year - _random.nextInt(3); // Enfants de 0 à 2 ans + final birthMonth = _random.nextInt(12) + 1; + final birthDay = _random.nextInt(28) + 1; // Simple, évite les pbs de jours/mois + return "${birthDay.toString().padLeft(2, '0')}/${birthMonth.toString().padLeft(2, '0')}/${birthYear}"; + } + } + + static bool boolean() => _random.nextBool(); + + static String motivation() { + int count = _random.nextInt(3) + 2; // 2 à 4 phrases + List chosenSnippets = []; + while(chosenSnippets.length < count) { + String snippet = _motivationSnippets[_random.nextInt(_motivationSnippets.length)]; + if (!chosenSnippets.contains(snippet)) { + chosenSnippets.add(snippet); + } + } + return chosenSnippets.join(' '); + } +} \ No newline at end of file diff --git a/frontend/lib/widgets/app_custom_checkbox.dart b/frontend/lib/widgets/app_custom_checkbox.dart index 63218a5..f5e0276 100644 --- a/frontend/lib/widgets/app_custom_checkbox.dart +++ b/frontend/lib/widgets/app_custom_checkbox.dart @@ -51,7 +51,7 @@ class AppCustomCheckbox extends StatelessWidget { Flexible( child: Text( label, - style: GoogleFonts.merienda(fontSize: 14), + style: GoogleFonts.merienda(fontSize: 16), overflow: TextOverflow.ellipsis, // Gérer le texte long ), ), diff --git a/frontend/lib/widgets/custom_app_text_field.dart b/frontend/lib/widgets/custom_app_text_field.dart index 8824451..5f700da 100644 --- a/frontend/lib/widgets/custom_app_text_field.dart +++ b/frontend/lib/widgets/custom_app_text_field.dart @@ -12,8 +12,8 @@ class CustomAppTextField extends StatefulWidget { final TextEditingController controller; final String labelText; final String hintText; - final double fieldHeight; final double fieldWidth; + final double fieldHeight; final bool obscureText; final TextInputType keyboardType; final String? Function(String?)? validator; @@ -23,14 +23,16 @@ class CustomAppTextField extends StatefulWidget { final bool readOnly; final VoidCallback? onTap; final IconData? suffixIcon; + final double labelFontSize; + final double inputFontSize; const CustomAppTextField({ super.key, required this.controller, required this.labelText, this.hintText = '', - this.fieldHeight = 50.0, this.fieldWidth = 300.0, + this.fieldHeight = 53.0, this.obscureText = false, this.keyboardType = TextInputType.text, this.validator, @@ -40,6 +42,8 @@ class CustomAppTextField extends StatefulWidget { this.readOnly = false, this.onTap, this.suffixIcon, + this.labelFontSize = 18.0, + this.inputFontSize = 18.0, }); @override @@ -61,6 +65,10 @@ class _CustomAppTextFieldState extends State { @override Widget build(BuildContext context) { + const double fontHeightMultiplier = 1.2; + const double internalVerticalPadding = 16.0; + final double dynamicFieldHeight = widget.fieldHeight; + return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -68,7 +76,7 @@ class _CustomAppTextFieldState extends State { Text( widget.labelText, style: GoogleFonts.merienda( - fontSize: 14, + fontSize: widget.labelFontSize, color: Colors.black87, fontWeight: FontWeight.w500, ), @@ -76,7 +84,7 @@ class _CustomAppTextFieldState extends State { const SizedBox(height: 6), SizedBox( width: widget.fieldWidth, - height: widget.fieldHeight, + height: dynamicFieldHeight, child: Stack( alignment: Alignment.centerLeft, children: [ @@ -87,7 +95,7 @@ class _CustomAppTextFieldState extends State { ), ), Padding( - padding: const EdgeInsets.only(left: 18.0, right: 18.0, bottom: 2.0), + padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 8.0), child: TextFormField( controller: widget.controller, obscureText: widget.obscureText, @@ -95,7 +103,10 @@ class _CustomAppTextFieldState extends State { enabled: widget.enabled, readOnly: widget.readOnly, onTap: widget.onTap, - style: GoogleFonts.merienda(fontSize: 15, color: widget.enabled ? Colors.black87 : Colors.grey), + style: GoogleFonts.merienda( + fontSize: widget.inputFontSize, + color: widget.enabled ? Colors.black87 : Colors.grey + ), validator: widget.validator ?? (value) { if (!widget.enabled || widget.readOnly) return null; @@ -106,13 +117,13 @@ class _CustomAppTextFieldState extends State { }, decoration: InputDecoration( hintText: widget.hintText, - hintStyle: GoogleFonts.merienda(fontSize: 15, color: Colors.black54.withOpacity(0.7)), + hintStyle: GoogleFonts.merienda(fontSize: widget.inputFontSize, color: Colors.black54.withOpacity(0.7)), border: InputBorder.none, - contentPadding: EdgeInsets.zero, + contentPadding: EdgeInsets.zero, suffixIcon: widget.suffixIcon != null ? Padding( padding: const EdgeInsets.only(right: 0.0), - child: Icon(widget.suffixIcon, color: Colors.black54, size: 20), + child: Icon(widget.suffixIcon, color: Colors.black54, size: widget.inputFontSize * 1.1), ) : null, isDense: true, diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 54b7cf6..1a73bb5 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -30,6 +30,7 @@ flutter: assets: - assets/images/ # Déclarer le dossier entier + - assets/cards/ # Nouveau dossier de cartes fonts: - family: Merienda diff --git a/lib/screens/auth/login_screen.dart b/lib/screens/auth/login_screen.dart new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/lib/screens/auth/login_screen.dart @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/screens/auth/parent_register_step3_screen.dart b/lib/screens/auth/parent_register_step3_screen.dart new file mode 100644 index 0000000..9857813 --- /dev/null +++ b/lib/screens/auth/parent_register_step3_screen.dart @@ -0,0 +1,22 @@ + CustomAppTextField( + controller: _firstNameController, + labelText: 'Prénom', + hintText: 'Facultatif si à naître', + isRequired: !widget.childData.isUnbornChild, + ), + const SizedBox(height: 6.0), + CustomAppTextField( + controller: _lastNameController, + labelText: 'Nom', + hintText: 'Nom de l\'enfant', + enabled: true, + ), + const SizedBox(height: 9.0), + 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, + ), \ No newline at end of file