refactor(inscription): Refonte complète du processus d'inscription - Modèles etdonnées: Suppression de placeholder_registration_data.dart, ajout de user_registration_data.dart, data_generator.dart et card_assets.dart - Interface utilisateur: Refonte des écrans d'inscription, amélioration des widgets, ajout de cartes colorées - Assets: Ajout de nouvelles cartes colorées - Configuration: Mise à jour de pubspec.yaml et app_router.dart
BIN
frontend/assets/cards/card_blue.png
Normal file
|
After Width: | Height: | Size: 656 KiB |
BIN
frontend/assets/cards/card_blue_h.png
Normal file
|
After Width: | Height: | Size: 693 KiB |
BIN
frontend/assets/cards/card_green.png
Normal file
|
After Width: | Height: | Size: 754 KiB |
BIN
frontend/assets/cards/card_green_h.png
Normal file
|
After Width: | Height: | Size: 784 KiB |
BIN
frontend/assets/cards/card_lavender.png
Normal file
|
After Width: | Height: | Size: 767 KiB |
BIN
frontend/assets/cards/card_lavender_h.png
Normal file
|
After Width: | Height: | Size: 755 KiB |
BIN
frontend/assets/cards/card_lime.png
Normal file
|
After Width: | Height: | Size: 851 KiB |
BIN
frontend/assets/cards/card_lime_h.png
Normal file
|
After Width: | Height: | Size: 850 KiB |
BIN
frontend/assets/cards/card_peach.png
Normal file
|
After Width: | Height: | Size: 517 KiB |
BIN
frontend/assets/cards/card_peach_h.png
Normal file
|
After Width: | Height: | Size: 560 KiB |
BIN
frontend/assets/cards/card_pink.png
Normal file
|
After Width: | Height: | Size: 908 KiB |
BIN
frontend/assets/cards/card_pink_h.png
Normal file
|
After Width: | Height: | Size: 909 KiB |
BIN
frontend/assets/cards/card_red.png
Normal file
|
After Width: | Height: | Size: 721 KiB |
BIN
frontend/assets/cards/card_red_h.png
Normal file
|
After Width: | Height: | Size: 744 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
BIN
frontend/assets/images/card_rose_h.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
25
frontend/lib/models/card_assets.dart
Normal file
@ -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);
|
||||||
|
}
|
||||||
@ -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<String> 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
97
frontend/lib/models/user_registration_data.dart
Normal file
@ -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<ChildData> children;
|
||||||
|
String motivationText;
|
||||||
|
bool cguAccepted;
|
||||||
|
|
||||||
|
UserRegistrationData({
|
||||||
|
ParentData? parent1Data,
|
||||||
|
this.parent2,
|
||||||
|
List<ChildData>? 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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_step4_screen.dart';
|
||||||
import '../screens/auth/parent_register_step5_screen.dart';
|
import '../screens/auth/parent_register_step5_screen.dart';
|
||||||
import '../screens/home/home_screen.dart';
|
import '../screens/home/home_screen.dart';
|
||||||
import '../models/placeholder_registration_data.dart';
|
import '../models/user_registration_data.dart';
|
||||||
|
|
||||||
class AppRouter {
|
class AppRouter {
|
||||||
static const String login = '/login';
|
static const String login = '/login';
|
||||||
@ -23,6 +23,12 @@ class AppRouter {
|
|||||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||||
Widget screen;
|
Widget screen;
|
||||||
bool slideTransition = false;
|
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) {
|
switch (settings.name) {
|
||||||
case login:
|
case login:
|
||||||
@ -30,32 +36,43 @@ class AppRouter {
|
|||||||
break;
|
break;
|
||||||
case registerChoice:
|
case registerChoice:
|
||||||
screen = const RegisterChoiceScreen();
|
screen = const RegisterChoiceScreen();
|
||||||
slideTransition = true; // Activer la transition pour cet écran
|
slideTransition = true;
|
||||||
break;
|
break;
|
||||||
case parentRegisterStep1:
|
case parentRegisterStep1:
|
||||||
screen = const ParentRegisterStep1Screen();
|
screen = const ParentRegisterStep1Screen();
|
||||||
slideTransition = true; // Activer la transition pour cet écran
|
slideTransition = true;
|
||||||
break;
|
break;
|
||||||
case parentRegisterStep2:
|
case parentRegisterStep2:
|
||||||
screen = const ParentRegisterStep2Screen();
|
if (args is UserRegistrationData) {
|
||||||
|
screen = ParentRegisterStep2Screen(registrationData: args);
|
||||||
|
} else {
|
||||||
|
screen = buildErrorScreen('2');
|
||||||
|
}
|
||||||
slideTransition = true;
|
slideTransition = true;
|
||||||
break;
|
break;
|
||||||
case parentRegisterStep3:
|
case parentRegisterStep3:
|
||||||
screen = const ParentRegisterStep3Screen();
|
if (args is UserRegistrationData) {
|
||||||
|
screen = ParentRegisterStep3Screen(registrationData: args);
|
||||||
|
} else {
|
||||||
|
screen = buildErrorScreen('3');
|
||||||
|
}
|
||||||
slideTransition = true;
|
slideTransition = true;
|
||||||
break;
|
break;
|
||||||
case parentRegisterStep4:
|
case parentRegisterStep4:
|
||||||
screen = const ParentRegisterStep4Screen();
|
if (args is UserRegistrationData) {
|
||||||
|
screen = ParentRegisterStep4Screen(registrationData: args);
|
||||||
|
} else {
|
||||||
|
screen = buildErrorScreen('4');
|
||||||
|
}
|
||||||
slideTransition = true;
|
slideTransition = true;
|
||||||
break;
|
break;
|
||||||
case parentRegisterStep5:
|
case parentRegisterStep5:
|
||||||
final args = settings.arguments as PlaceholderRegistrationData?;
|
if (args is UserRegistrationData) {
|
||||||
if (args != null) {
|
|
||||||
screen = ParentRegisterStep5Screen(registrationData: args);
|
screen = ParentRegisterStep5Screen(registrationData: args);
|
||||||
} else {
|
} else {
|
||||||
print("Erreur: Données d'inscription manquantes pour l'étape 5");
|
screen = buildErrorScreen('5');
|
||||||
screen = const RegisterChoiceScreen();
|
|
||||||
}
|
}
|
||||||
|
slideTransition = true;
|
||||||
break;
|
break;
|
||||||
case home:
|
case home:
|
||||||
screen = const HomeScreen();
|
screen = const HomeScreen();
|
||||||
@ -72,22 +89,16 @@ class AppRouter {
|
|||||||
return PageRouteBuilder(
|
return PageRouteBuilder(
|
||||||
pageBuilder: (context, animation, secondaryAnimation) => screen,
|
pageBuilder: (context, animation, secondaryAnimation) => screen,
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
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 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 tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
||||||
var offsetAnimation = animation.drive(tween);
|
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 {
|
} else {
|
||||||
// Transition par défaut pour les autres écrans
|
|
||||||
return MaterialPageRoute(builder: (_) => screen);
|
return MaterialPageRoute(builder: (_) => screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'dart:math' as math; // Pour la rotation du chevron
|
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 {
|
class ParentRegisterStep1Screen extends StatefulWidget {
|
||||||
const ParentRegisterStep1Screen({super.key});
|
const ParentRegisterStep1Screen({super.key});
|
||||||
@ -11,17 +15,42 @@ class ParentRegisterStep1Screen extends StatefulWidget {
|
|||||||
|
|
||||||
class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
|
class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late UserRegistrationData _registrationData;
|
||||||
|
|
||||||
// Contrôleurs pour les champs
|
// Contrôleurs pour les champs (restauration CP et Ville)
|
||||||
final _lastNameController = TextEditingController();
|
final _lastNameController = TextEditingController();
|
||||||
final _firstNameController = TextEditingController();
|
final _firstNameController = TextEditingController();
|
||||||
final _phoneController = TextEditingController();
|
final _phoneController = TextEditingController();
|
||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _confirmPasswordController = TextEditingController();
|
final _confirmPasswordController = TextEditingController();
|
||||||
final _addressController = TextEditingController();
|
final _addressController = TextEditingController(); // Rue seule
|
||||||
final _postalCodeController = TextEditingController();
|
final _postalCodeController = TextEditingController(); // Restauré
|
||||||
final _cityController = TextEditingController();
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -61,13 +90,13 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
|
|||||||
children: [
|
children: [
|
||||||
// Indicateur d'étape (à rendre dynamique)
|
// Indicateur d'étape (à rendre dynamique)
|
||||||
Text(
|
Text(
|
||||||
'Étape 1/X',
|
'Étape 1/5',
|
||||||
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
|
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// Texte d'instruction
|
// Texte d'instruction
|
||||||
Text(
|
Text(
|
||||||
'Merci de renseigner les informations du premier parent :',
|
'Informations du Parent Principal',
|
||||||
style: GoogleFonts.merienda(
|
style: GoogleFonts.merienda(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -80,10 +109,11 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
|
|||||||
// Carte jaune contenant le formulaire
|
// Carte jaune contenant le formulaire
|
||||||
Container(
|
Container(
|
||||||
width: screenSize.width * 0.6,
|
width: screenSize.width * 0.6,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
|
padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 50),
|
||||||
decoration: const BoxDecoration(
|
constraints: const BoxConstraints(minHeight: 570),
|
||||||
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: AssetImage('assets/images/card_yellow_h.png'),
|
image: AssetImage(CardColorHorizontal.peach.path),
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -94,35 +124,49 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildTextField(_lastNameController, 'Nom', hintText: 'Votre nom de famille')),
|
Expanded(flex: 12, child: CustomAppTextField(controller: _lastNameController, labelText: 'Nom', hintText: 'Votre nom de famille', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
|
||||||
const SizedBox(width: 20),
|
Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
|
||||||
Expanded(child: _buildTextField(_firstNameController, 'Prénom', hintText: 'Votre prénom')),
|
Expanded(flex: 12, child: CustomAppTextField(controller: _firstNameController, labelText: 'Prénom', hintText: 'Votre prénom', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildTextField(_phoneController, 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Votre numéro de téléphone')),
|
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)),
|
||||||
const SizedBox(width: 20),
|
Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
|
||||||
Expanded(child: _buildTextField(_emailController, 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Votre adresse e-mail')),
|
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),
|
const SizedBox(height: 20),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildTextField(_passwordController, 'Mot de passe', obscureText: true, hintText: 'Créez votre 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) {
|
||||||
const SizedBox(width: 20),
|
if (value == null || value.isEmpty) return 'Mot de passe requis';
|
||||||
Expanded(child: _buildTextField(_confirmPasswordController, 'Confirmation', obscureText: true, hintText: 'Confirmez le mot de passe')),
|
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),
|
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),
|
const SizedBox(height: 20),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
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),
|
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<ParentRegisterStep1Screen> {
|
|||||||
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState?.validate() ?? false) {
|
if (_formKey.currentState?.validate() ?? false) {
|
||||||
// TODO: Sauvegarder les données du parent 1
|
_registrationData.updateParent1(
|
||||||
Navigator.pushNamed(context, '/parent-register/step2'); // Naviguer vers l'étape 2
|
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',
|
tooltip: 'Suivant',
|
||||||
@ -168,59 +223,4 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,9 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'dart:math' as math; // Pour la rotation du chevron
|
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 {
|
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
|
@override
|
||||||
State<ParentRegisterStep2Screen> createState() => _ParentRegisterStep2ScreenState();
|
State<ParentRegisterStep2Screen> createState() => _ParentRegisterStep2ScreenState();
|
||||||
@ -11,25 +17,54 @@ class ParentRegisterStep2Screen extends StatefulWidget {
|
|||||||
|
|
||||||
class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
|
class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late UserRegistrationData _registrationData; // Copie locale pour modification
|
||||||
|
|
||||||
// TODO: Recevoir les infos du parent 1 pour pré-remplir l'adresse
|
bool _addParent2 = true; // Pour le test, on ajoute toujours le parent 2
|
||||||
// String? _parent1Address;
|
bool _sameAddressAsParent1 = false; // Peut être généré aléatoirement aussi
|
||||||
// String? _parent1PostalCode;
|
|
||||||
// String? _parent1City;
|
|
||||||
|
|
||||||
bool _addParent2 = false; // Par défaut, on n'ajoute pas le parent 2
|
// Contrôleurs pour les champs du parent 2 (restauration CP et Ville)
|
||||||
bool _sameAddressAsParent1 = false;
|
|
||||||
|
|
||||||
// Contrôleurs pour les champs du parent 2
|
|
||||||
final _lastNameController = TextEditingController();
|
final _lastNameController = TextEditingController();
|
||||||
final _firstNameController = TextEditingController();
|
final _firstNameController = TextEditingController();
|
||||||
final _phoneController = TextEditingController();
|
final _phoneController = TextEditingController();
|
||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _confirmPasswordController = TextEditingController();
|
final _confirmPasswordController = TextEditingController();
|
||||||
final _addressController = TextEditingController();
|
final _addressController = TextEditingController(); // Rue seule
|
||||||
final _postalCodeController = TextEditingController();
|
final _postalCodeController = TextEditingController(); // Restauré
|
||||||
final _cityController = TextEditingController();
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -44,10 +79,8 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
|
|||||||
_cityController.dispose();
|
_cityController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper pour activer/désactiver tous les champs sauf l'adresse
|
|
||||||
bool get _parent2FieldsEnabled => _addParent2;
|
bool get _parent2FieldsEnabled => _addParent2;
|
||||||
// Helper pour activer/désactiver les champs d'adresse
|
|
||||||
bool get _addressFieldsEnabled => _addParent2 && !_sameAddressAsParent1;
|
bool get _addressFieldsEnabled => _addParent2 && !_sameAddressAsParent1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -57,174 +90,104 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
// Fond papier
|
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Image.asset(
|
child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat),
|
||||||
'assets/images/paper2.png',
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
repeat: ImageRepeat.repeat,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
// Contenu centré
|
|
||||||
Center(
|
Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// Indicateur d'étape
|
Text('Étape 2/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
|
||||||
Text(
|
|
||||||
'Étape 2/X', // Mettre à jour le numéro d'étape total
|
|
||||||
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// Texte d'instruction
|
|
||||||
Text(
|
Text(
|
||||||
'Renseignez les informations du deuxième parent (optionnel) :',
|
'Informations du Deuxième Parent (Optionnel)',
|
||||||
style: GoogleFonts.merienda(
|
style: GoogleFonts.merienda(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
// Carte bleue contenant le formulaire
|
|
||||||
Container(
|
Container(
|
||||||
width: screenSize.width * 0.6,
|
width: screenSize.width * 0.6,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
|
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
|
||||||
decoration: const BoxDecoration( // Retour à la décoration
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(image: AssetImage(CardColorHorizontal.blue.path), fit: BoxFit.fill),
|
||||||
image: AssetImage('assets/images/card_blue_h.png'), // Utilisation de l'image horizontale
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
// Suppression du Stack et Transform.rotate
|
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: SingleChildScrollView( // Le SingleChildScrollView redevient l'enfant direct
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// --- Interrupteurs sur une ligne ---
|
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// Option 1: Ajouter Parent 2
|
Expanded(
|
||||||
Expanded(
|
flex: 12,
|
||||||
child: Row(
|
child: Row(children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
const Icon(Icons.person_add_alt_1, size: 20), const SizedBox(width: 8),
|
||||||
children: [
|
Flexible(child: Text('Ajouter Parent 2 ?', style: GoogleFonts.merienda(fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis)),
|
||||||
const Icon(Icons.person_add_alt_1, size: 20),
|
const Spacer(),
|
||||||
const SizedBox(width: 8),
|
Switch(value: _addParent2, onChanged: (val) => setState(() {
|
||||||
Flexible(
|
_addParent2 = val ?? false;
|
||||||
child: Text(
|
if (_addParent2) _generateAndFillParent2Data(); else _clearParent2Fields();
|
||||||
'Ajouter Parent 2 ?',
|
}), activeColor: Theme.of(context).primaryColor),
|
||||||
style: GoogleFonts.merienda(fontWeight: FontWeight.bold),
|
]),
|
||||||
overflow: TextOverflow.ellipsis,
|
),
|
||||||
),
|
Expanded(flex: 1, child: const SizedBox()),
|
||||||
),
|
Expanded(
|
||||||
_buildCustomSwitch(
|
flex: 12,
|
||||||
value: _addParent2,
|
child: Row(children: [
|
||||||
onChanged: (bool? newValue) {
|
Icon(Icons.home_work_outlined, size: 20, color: _addParent2 ? null : Colors.grey),
|
||||||
final bool actualValue = newValue ?? false;
|
const SizedBox(width: 8),
|
||||||
setState(() {
|
Flexible(child: Text('Même Adresse ?', style: GoogleFonts.merienda(color: _addParent2 ? null : Colors.grey), overflow: TextOverflow.ellipsis)),
|
||||||
_addParent2 = actualValue;
|
const Spacer(),
|
||||||
if (!_addParent2) {
|
Switch(value: _sameAddressAsParent1, onChanged: _addParent2 ? (val) => setState(() {
|
||||||
_formKey.currentState?.reset();
|
_sameAddressAsParent1 = val ?? false;
|
||||||
_lastNameController.clear();
|
if (_sameAddressAsParent1) {
|
||||||
_firstNameController.clear();
|
_addressController.text = _registrationData.parent1.address;
|
||||||
_phoneController.clear();
|
_postalCodeController.text = _registrationData.parent1.postalCode;
|
||||||
_emailController.clear();
|
_cityController.text = _registrationData.parent1.city;
|
||||||
_passwordController.clear();
|
} else {
|
||||||
_confirmPasswordController.clear();
|
_addressController.text = DataGenerator.address();
|
||||||
_addressController.clear();
|
_postalCodeController.text = DataGenerator.postalCode();
|
||||||
_postalCodeController.clear();
|
_cityController.text = DataGenerator.city();
|
||||||
_cityController.clear();
|
}
|
||||||
_sameAddressAsParent1 = false;
|
}) : null, activeColor: Theme.of(context).primaryColor),
|
||||||
}
|
]),
|
||||||
});
|
),
|
||||||
},
|
]),
|
||||||
),
|
const SizedBox(height: 25),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildTextField(_lastNameController, 'Nom', hintText: '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)),
|
||||||
const SizedBox(width: 20),
|
Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
|
||||||
Expanded(child: _buildTextField(_firstNameController, 'Prénom', hintText: 'Prénom du deuxième parent', enabled: _parent2FieldsEnabled)),
|
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),
|
const SizedBox(height: 20),
|
||||||
// Téléphone & Email
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildTextField(_phoneController, 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Son numéro de téléphone', 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)),
|
||||||
const SizedBox(width: 20),
|
Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
|
||||||
Expanded(child: _buildTextField(_emailController, 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Son adresse e-mail', enabled: _parent2FieldsEnabled)),
|
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),
|
const SizedBox(height: 20),
|
||||||
// Mot de passe
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildTextField(_passwordController, 'Mot de passe', obscureText: true, hintText: '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)),
|
||||||
const SizedBox(width: 20),
|
Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
|
||||||
Expanded(child: _buildTextField(_confirmPasswordController, 'Confirmation', obscureText: true, hintText: 'Confirmer son mot de passe', enabled: _parent2FieldsEnabled)),
|
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),
|
const SizedBox(height: 20),
|
||||||
|
CustomAppTextField(controller: _addressController, labelText: 'Adresse (N° et Rue)', hintText: 'Son numéro et nom de rue', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity),
|
||||||
// --- Champs Adresse (conditionnels) ---
|
|
||||||
_buildTextField(_addressController, 'Adresse (Rue)', hintText: 'Son numéro et nom de rue', enabled: _addressFieldsEnabled),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
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),
|
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<ParentRegisterStep2Screen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Chevron de navigation gauche (Retour)
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: screenSize.height / 2 - 20,
|
top: screenSize.height / 2 - 20,
|
||||||
left: 40,
|
left: 40,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Transform(
|
icon: Transform(alignment: Alignment.center, transform: Matrix4.rotationY(math.pi), child: Image.asset('assets/images/chevron_right.png', height: 40)),
|
||||||
alignment: Alignment.center,
|
onPressed: () => Navigator.pop(context),
|
||||||
transform: Matrix4.rotationY(math.pi),
|
|
||||||
child: Image.asset('assets/images/chevron_right.png', height: 40),
|
|
||||||
),
|
|
||||||
onPressed: () => Navigator.pop(context), // Retour à l'étape 1
|
|
||||||
tooltip: 'Retour',
|
tooltip: 'Retour',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Chevron de navigation droit (Suivant)
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: screenSize.height / 2 - 20,
|
top: screenSize.height / 2 - 20,
|
||||||
right: 40,
|
right: 40,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Si on n'ajoute pas de parent 2, on passe directement
|
if (!_addParent2 || (_formKey.currentState?.validate() ?? false)) {
|
||||||
if (!_addParent2) {
|
if (_addParent2) {
|
||||||
// Naviguer vers l'étape 3 (enfants)
|
_registrationData.updateParent2(
|
||||||
print('Passer à l\'étape 3 (enfants) - Sans Parent 2');
|
ParentData(
|
||||||
Navigator.pushNamed(context, '/parent-register/step3');
|
firstName: _firstNameController.text,
|
||||||
return;
|
lastName: _lastNameController.text,
|
||||||
}
|
address: _sameAddressAsParent1 ? _registrationData.parent1.address : _addressController.text,
|
||||||
// Si on ajoute un parent 2
|
postalCode: _sameAddressAsParent1 ? _registrationData.parent1.postalCode : _postalCodeController.text,
|
||||||
// Valider seulement si on n'utilise PAS la même adresse
|
city: _sameAddressAsParent1 ? _registrationData.parent1.city : _cityController.text,
|
||||||
bool isFormValid = true;
|
phone: _phoneController.text,
|
||||||
// TODO: Remettre la validation quand elle sera prête
|
email: _emailController.text,
|
||||||
/*
|
password: _passwordController.text,
|
||||||
if (!_sameAddressAsParent1) {
|
)
|
||||||
isFormValid = _formKey.currentState?.validate() ?? false;
|
);
|
||||||
}
|
} else {
|
||||||
*/
|
_registrationData.updateParent2(null);
|
||||||
|
}
|
||||||
if (isFormValid) {
|
Navigator.pushNamed(context, '/parent-register/step3', arguments: _registrationData);
|
||||||
// TODO: Sauvegarder les données du parent 2
|
|
||||||
print('Passer à l\'étape 3 (enfants) - Avec Parent 2');
|
|
||||||
Navigator.pushNamed(context, '/parent-register/step3');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: 'Suivant',
|
tooltip: 'Suivant',
|
||||||
@ -290,85 +242,14 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NOUVEAU WIDGET ---
|
void _clearParent2Fields() {
|
||||||
// Widget pour construire un switch personnalisé avec images
|
_formKey.currentState?.reset();
|
||||||
Widget _buildCustomSwitch({required bool value, required ValueChanged<bool?>? onChanged}) {
|
_lastNameController.clear(); _firstNameController.clear(); _phoneController.clear();
|
||||||
// --- DEBUG ---
|
_emailController.clear(); _passwordController.clear(); _confirmPasswordController.clear();
|
||||||
print("Building Custom Switch with value: $value");
|
_addressController.clear();
|
||||||
// -------------
|
_postalCodeController.clear();
|
||||||
const double switchHeight = 25.0;
|
_cityController.clear();
|
||||||
const double switchWidth = 40.0;
|
_sameAddressAsParent1 = false;
|
||||||
|
setState(() {});
|
||||||
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;
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'dart:math' as math; // Pour la rotation du chevron
|
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 '../../widgets/hover_relief_widget.dart'; // Import du nouveau widget
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
// import 'package:image_cropper/image_cropper.dart'; // Supprimé
|
// 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 'package:flutter/foundation.dart' show kIsWeb; // Import pour kIsWeb
|
||||||
import '../../widgets/custom_app_text_field.dart'; // Import du nouveau widget TextField
|
import '../../widgets/custom_app_text_field.dart'; // Import du nouveau widget TextField
|
||||||
import '../../widgets/app_custom_checkbox.dart'; // Import du nouveau widget Checkbox
|
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
|
// La classe _ChildFormData est supprimée car on utilise ChildData du modèle
|
||||||
class _ChildFormData {
|
|
||||||
final Key key; // Pour aider Flutter à identifier les widgets dans une liste
|
|
||||||
final TextEditingController firstNameController;
|
|
||||||
final TextEditingController lastNameController;
|
|
||||||
final TextEditingController dobController;
|
|
||||||
bool photoConsent;
|
|
||||||
bool multipleBirth;
|
|
||||||
bool isUnbornChild;
|
|
||||||
File? imageFile;
|
|
||||||
|
|
||||||
_ChildFormData({
|
|
||||||
required this.key,
|
|
||||||
String initialFirstName = '',
|
|
||||||
String initialLastName = '',
|
|
||||||
String initialDob = '',
|
|
||||||
this.photoConsent = false,
|
|
||||||
this.multipleBirth = false,
|
|
||||||
this.isUnbornChild = false,
|
|
||||||
this.imageFile,
|
|
||||||
}) : firstNameController = TextEditingController(text: initialFirstName),
|
|
||||||
lastNameController = TextEditingController(text: initialLastName),
|
|
||||||
dobController = TextEditingController(text: initialDob);
|
|
||||||
|
|
||||||
// Méthode pour disposer les contrôleurs
|
|
||||||
void dispose() {
|
|
||||||
firstNameController.dispose();
|
|
||||||
lastNameController.dispose();
|
|
||||||
dobController.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ParentRegisterStep3Screen extends StatefulWidget {
|
class ParentRegisterStep3Screen extends StatefulWidget {
|
||||||
const ParentRegisterStep3Screen({super.key});
|
final UserRegistrationData registrationData; // Accepte les données
|
||||||
|
|
||||||
|
const ParentRegisterStep3Screen({super.key, required this.registrationData});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ParentRegisterStep3Screen> createState() => _ParentRegisterStep3ScreenState();
|
State<ParentRegisterStep3Screen> createState() => _ParentRegisterStep3ScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||||
// TODO: Gérer une liste d'enfants et leurs contrôleurs respectifs
|
late UserRegistrationData _registrationData; // Stocke l'état complet
|
||||||
// List<ChildData> _children = [ChildData()]; // Commencer avec un enfant
|
final ScrollController _scrollController = ScrollController(); // Pour le défilement horizontal
|
||||||
final _formKey = GlobalKey<FormState>(); // Une clé par enfant sera nécessaire si validation complexe
|
|
||||||
|
|
||||||
// Liste pour stocker les données de chaque enfant
|
|
||||||
List<_ChildFormData> _childrenDataList = [];
|
|
||||||
final ScrollController _scrollController = ScrollController(); // Ajout du ScrollController
|
|
||||||
bool _isScrollable = false;
|
bool _isScrollable = false;
|
||||||
bool _showLeftFade = false;
|
bool _showLeftFade = false;
|
||||||
bool _showRightFade = 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<CardColorVertical> _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<int, GlobalKey<FormState>> _childFormKeys = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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);
|
_scrollController.addListener(_scrollListener);
|
||||||
// Appel initial pour définir l'état des fondus après le premier layout
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollListener());
|
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// Disposer les contrôleurs de tous les enfants
|
_scrollController.removeListener(_scrollListener);
|
||||||
for (var childData in _childrenDataList) {
|
|
||||||
childData.dispose();
|
|
||||||
}
|
|
||||||
_scrollController.removeListener(_scrollListener); // Ne pas oublier de retirer le listener
|
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _scrollListener() {
|
void _scrollListener() {
|
||||||
if (!_scrollController.hasClients) return; // S'assurer que le controller est attaché
|
if (!_scrollController.hasClients) return;
|
||||||
|
|
||||||
final position = _scrollController.position;
|
final position = _scrollController.position;
|
||||||
final newIsScrollable = position.maxScrollExtent > 0.0;
|
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);
|
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));
|
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(() {
|
setState(() {
|
||||||
_isScrollable = newIsScrollable;
|
_isScrollable = newIsScrollable;
|
||||||
_showLeftFade = newShowLeftFade;
|
_showLeftFade = newShowLeftFade;
|
||||||
@ -103,110 +81,99 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _addChild() {
|
void _addChild() {
|
||||||
String initialLastName = '';
|
|
||||||
if (_childrenDataList.isNotEmpty) {
|
|
||||||
initialLastName = _childrenDataList.first.lastNameController.text;
|
|
||||||
}
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_childrenDataList.add(_ChildFormData(
|
bool isUnborn = DataGenerator.boolean();
|
||||||
key: UniqueKey(),
|
// Déterminer la couleur de la carte pour le nouvel enfant
|
||||||
initialLastName: initialLastName,
|
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<FormState>();
|
||||||
});
|
});
|
||||||
// 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((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_scrollListener(); // Mettre à jour l'état des fondus
|
_scrollListener();
|
||||||
if (_scrollController.hasClients && _scrollController.position.maxScrollExtent > 0.0) {
|
if (_scrollController.hasClients && _scrollController.position.maxScrollExtent > 0.0) {
|
||||||
_scrollController.animateTo(
|
_scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
|
||||||
_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<void> _pickImage(int childIndex) async {
|
Future<void> _pickImage(int childIndex) async {
|
||||||
final ImagePicker picker = ImagePicker();
|
final ImagePicker picker = ImagePicker();
|
||||||
try {
|
try {
|
||||||
final XFile? pickedFile = await picker.pickImage(
|
final XFile? pickedFile = await picker.pickImage(
|
||||||
source: ImageSource.gallery,
|
source: ImageSource.gallery, imageQuality: 70, maxWidth: 1024, maxHeight: 1024);
|
||||||
imageQuality: 70,
|
|
||||||
maxWidth: 1024,
|
|
||||||
maxHeight: 1024,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pickedFile != null) {
|
if (pickedFile != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (childIndex < _childrenDataList.length) {
|
if (childIndex < _registrationData.children.length) {
|
||||||
_childrenDataList[childIndex].imageFile = File(pickedFile.path);
|
_registrationData.children[childIndex].imageFile = File(pickedFile.path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} // Fin de if (pickedFile != null)
|
}
|
||||||
|
} catch (e) { print("Erreur image: $e"); }
|
||||||
} catch (e) {
|
|
||||||
print("Erreur lors de la sélection de l'image: $e");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _selectDate(BuildContext context, int childIndex) async {
|
Future<void> _selectDate(BuildContext context, int childIndex) async {
|
||||||
final _ChildFormData currentChild = _childrenDataList[childIndex];
|
final ChildData currentChild = _registrationData.children[childIndex];
|
||||||
final DateTime now = DateTime.now();
|
final DateTime now = DateTime.now();
|
||||||
DateTime initialDatePickerDate = now;
|
DateTime initialDatePickerDate = now;
|
||||||
DateTime firstDatePickerDate = DateTime(1980);
|
DateTime firstDatePickerDate = DateTime(1980); DateTime lastDatePickerDate = now;
|
||||||
DateTime lastDatePickerDate = now;
|
|
||||||
|
|
||||||
if (currentChild.isUnbornChild) {
|
if (currentChild.isUnbornChild) {
|
||||||
firstDatePickerDate = now;
|
firstDatePickerDate = now; lastDatePickerDate = now.add(const Duration(days: 300));
|
||||||
lastDatePickerDate = now.add(const Duration(days: 300));
|
if (currentChild.dob.isNotEmpty) {
|
||||||
if (currentChild.dobController.text.isNotEmpty) {
|
|
||||||
try {
|
try {
|
||||||
List<String> parts = currentChild.dobController.text.split('/');
|
List<String> parts = currentChild.dob.split('/');
|
||||||
DateTime? parsedDate = DateTime.tryParse("${parts[2]}-${parts[1].padLeft(2, '0')}-${parts[0].padLeft(2, '0')}");
|
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)) {
|
if (parsedDate != null && !parsedDate.isBefore(firstDatePickerDate) && !parsedDate.isAfter(lastDatePickerDate)) {
|
||||||
initialDatePickerDate = parsedDate;
|
initialDatePickerDate = parsedDate;
|
||||||
}
|
}
|
||||||
} catch (e) { /* Ignorer */ }
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (currentChild.dobController.text.isNotEmpty) {
|
if (currentChild.dob.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
List<String> parts = currentChild.dobController.text.split('/');
|
List<String> parts = currentChild.dob.split('/');
|
||||||
DateTime? parsedDate = DateTime.tryParse("${parts[2]}-${parts[1].padLeft(2, '0')}-${parts[0].padLeft(2, '0')}");
|
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)) {
|
if (parsedDate != null && !parsedDate.isBefore(firstDatePickerDate) && !parsedDate.isAfter(lastDatePickerDate)) {
|
||||||
initialDatePickerDate = parsedDate;
|
initialDatePickerDate = parsedDate;
|
||||||
}
|
}
|
||||||
} catch (e) { /* Ignorer */ }
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final DateTime? picked = await showDatePicker(
|
final DateTime? picked = await showDatePicker(
|
||||||
context: context,
|
context: context, initialDate: initialDatePickerDate, firstDate: firstDatePickerDate,
|
||||||
initialDate: initialDatePickerDate,
|
lastDate: lastDatePickerDate, locale: const Locale('fr', 'FR'),
|
||||||
firstDate: firstDatePickerDate,
|
|
||||||
lastDate: lastDatePickerDate,
|
|
||||||
locale: const Locale('fr', 'FR'),
|
|
||||||
);
|
);
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
setState(() {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenSize = MediaQuery.of(context).size;
|
final screenSize = MediaQuery.of(context).size;
|
||||||
@ -217,101 +184,79 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
|||||||
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),
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 40.0),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
Text('Étape 3/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
|
||||||
children: [
|
const SizedBox(height: 10),
|
||||||
Text('Étape 3/X', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
|
Text(
|
||||||
const SizedBox(height: 10),
|
'Informations Enfants',
|
||||||
Text(
|
style: GoogleFonts.merienda(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||||
'Merci de renseigner les informations de/vos enfant(s) :',
|
textAlign: TextAlign.center,
|
||||||
style: GoogleFonts.merienda(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87),
|
),
|
||||||
textAlign: TextAlign.center,
|
const SizedBox(height: 30),
|
||||||
),
|
Padding(
|
||||||
const SizedBox(height: 30),
|
padding: const EdgeInsets.symmetric(horizontal: 150.0),
|
||||||
Padding( // Ajout du Padding pour les marges latérales
|
child: SizedBox(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 150.0), // Marge de 150px de chaque côté
|
height: 684.0,
|
||||||
child: SizedBox(
|
child: ShaderMask(
|
||||||
height: 500,
|
shaderCallback: (Rect bounds) {
|
||||||
child: ShaderMask(
|
final Color leftFade = (_isScrollable && _showLeftFade) ? Colors.transparent : Colors.black;
|
||||||
shaderCallback: (Rect bounds) {
|
final Color rightFade = (_isScrollable && _showRightFade) ? Colors.transparent : Colors.black;
|
||||||
// Déterminer les couleurs du gradient en fonction de l'état de défilement
|
if (!_isScrollable) { return LinearGradient(colors: const <Color>[Colors.black, Colors.black, Colors.black, Colors.black], stops: const [0.0, _fadeExtent, 1.0 - _fadeExtent, 1.0],).createShader(bounds); }
|
||||||
final Color leftFade = (_isScrollable && _showLeftFade) ? Colors.transparent : Colors.black;
|
return LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: <Color>[ leftFade, Colors.black, Colors.black, rightFade ], stops: const [0.0, _fadeExtent, 1.0 - _fadeExtent, 1.0], ).createShader(bounds);
|
||||||
final Color rightFade = (_isScrollable && _showRightFade) ? Colors.transparent : Colors.black;
|
},
|
||||||
|
blendMode: BlendMode.dstIn,
|
||||||
// Si ce n'est pas scrollable du tout, pas de fondu.
|
child: Scrollbar(
|
||||||
if (!_isScrollable) {
|
controller: _scrollController,
|
||||||
return LinearGradient(
|
thumbVisibility: true,
|
||||||
colors: const <Color>[Colors.black, Colors.black, Colors.black, Colors.black],
|
child: ListView.builder(
|
||||||
stops: const [0.0, _fadeExtent, 1.0 - _fadeExtent, 1.0],
|
controller: _scrollController,
|
||||||
).createShader(bounds);
|
scrollDirection: Axis.horizontal,
|
||||||
}
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
itemCount: _registrationData.children.length + 1,
|
||||||
return LinearGradient(
|
itemBuilder: (context, index) {
|
||||||
begin: Alignment.centerLeft,
|
if (index < _registrationData.children.length) {
|
||||||
end: Alignment.centerRight,
|
// Carte Enfant
|
||||||
colors: <Color>[
|
return Padding(
|
||||||
leftFade, // Bord gauche
|
padding: const EdgeInsets.only(right: 20.0),
|
||||||
Colors.black, // Devient opaque
|
child: _ChildCardWidget(
|
||||||
Colors.black, // Reste opaque
|
key: ValueKey(_registrationData.children[index].hashCode), // Utiliser une clé basée sur les données
|
||||||
rightFade // Bord droit
|
childData: _registrationData.children[index],
|
||||||
],
|
childIndex: index,
|
||||||
stops: const [0.0, _fadeExtent, 1.0 - _fadeExtent, 1.0], // 5% de fondu sur chaque bord
|
onPickImage: () => _pickImage(index),
|
||||||
).createShader(bounds);
|
onDateSelect: () => _selectDate(context, index),
|
||||||
},
|
onFirstNameChanged: (value) => setState(() => _registrationData.children[index].firstName = value),
|
||||||
blendMode: BlendMode.dstIn,
|
onLastNameChanged: (value) => setState(() => _registrationData.children[index].lastName = value),
|
||||||
child: Scrollbar( // Ajout du Scrollbar
|
onTogglePhotoConsent: (newValue) => setState(() => _registrationData.children[index].photoConsent = newValue),
|
||||||
controller: _scrollController, // Utiliser le même contrôleur
|
onToggleMultipleBirth: (newValue) => setState(() => _registrationData.children[index].multipleBirth = newValue),
|
||||||
thumbVisibility: true, // Rendre la thumb toujours visible pour le web si souhaité, ou la laisser adaptative
|
onToggleIsUnborn: (newValue) => setState(() {
|
||||||
child: ListView.builder(
|
_registrationData.children[index].isUnbornChild = newValue;
|
||||||
controller: _scrollController,
|
// Générer une nouvelle date si on change le statut
|
||||||
scrollDirection: Axis.horizontal,
|
_registrationData.children[index].dob = DataGenerator.dob(isUnborn: newValue);
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
}),
|
||||||
itemCount: _childrenDataList.length + 1,
|
onRemove: () => _removeChild(index),
|
||||||
itemBuilder: (context, index) {
|
canBeRemoved: _registrationData.children.length > 1,
|
||||||
if (index < _childrenDataList.length) {
|
),
|
||||||
// Carte Enfant
|
);
|
||||||
return Padding(
|
} else {
|
||||||
padding: const EdgeInsets.only(right: 20.0), // Espace entre les cartes
|
// Bouton Ajouter
|
||||||
child: _ChildCardWidget(
|
return Center(
|
||||||
key: _childrenDataList[index].key, // Passer la clé unique
|
child: HoverReliefWidget(
|
||||||
childData: _childrenDataList[index],
|
onPressed: _addChild,
|
||||||
childIndex: index,
|
borderRadius: BorderRadius.circular(15),
|
||||||
onPickImage: () => _pickImage(index),
|
child: Image.asset('assets/images/plus.png', height: 80, width: 80),
|
||||||
onDateSelect: () => _selectDate(context, index),
|
),
|
||||||
onTogglePhotoConsent: (newValue) {
|
);
|
||||||
setState(() => _childrenDataList[index].photoConsent = newValue);
|
}
|
||||||
},
|
},
|
||||||
onToggleMultipleBirth: (newValue) {
|
|
||||||
setState(() => _childrenDataList[index].multipleBirth = newValue);
|
|
||||||
},
|
|
||||||
onToggleIsUnborn: (newValue) {
|
|
||||||
setState(() => _childrenDataList[index].isUnbornChild = newValue);
|
|
||||||
},
|
|
||||||
onRemove: () => _removeChild(_childrenDataList[index].key),
|
|
||||||
canBeRemoved: _childrenDataList.length > 1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Bouton Ajouter
|
|
||||||
return Center( // Pour centrer le bouton dans l'espace disponible
|
|
||||||
child: HoverReliefWidget(
|
|
||||||
onPressed: _addChild,
|
|
||||||
borderRadius: BorderRadius.circular(15),
|
|
||||||
child: Image.asset('assets/images/plus.png', height: 80, width: 80),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20), // Espace optionnel après la liste
|
),
|
||||||
],
|
const SizedBox(height: 20),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Chevrons de navigation
|
// Chevrons de navigation
|
||||||
@ -330,8 +275,8 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print('Passer à l\'étape 4 (Situation familiale et CGU)');
|
// TODO: Validation (si nécessaire)
|
||||||
Navigator.pushNamed(context, '/parent-register/step4');
|
Navigator.pushNamed(context, '/parent-register/step4', arguments: _registrationData);
|
||||||
},
|
},
|
||||||
tooltip: 'Suivant',
|
tooltip: 'Suivant',
|
||||||
),
|
),
|
||||||
@ -342,24 +287,28 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nouveau Widget pour la carte enfant
|
// Widget pour la carte enfant (adapté pour prendre ChildData et des callbacks)
|
||||||
class _ChildCardWidget extends StatelessWidget {
|
class _ChildCardWidget extends StatefulWidget { // Transformé en StatefulWidget pour gérer les contrôleurs internes
|
||||||
final _ChildFormData childData;
|
final ChildData childData;
|
||||||
final int childIndex; // Utile pour certains callbacks ou logging
|
final int childIndex;
|
||||||
final VoidCallback onPickImage;
|
final VoidCallback onPickImage;
|
||||||
final VoidCallback onDateSelect;
|
final VoidCallback onDateSelect;
|
||||||
|
final ValueChanged<String> onFirstNameChanged;
|
||||||
|
final ValueChanged<String> onLastNameChanged;
|
||||||
final ValueChanged<bool> onTogglePhotoConsent;
|
final ValueChanged<bool> onTogglePhotoConsent;
|
||||||
final ValueChanged<bool> onToggleMultipleBirth;
|
final ValueChanged<bool> onToggleMultipleBirth;
|
||||||
final ValueChanged<bool> onToggleIsUnborn;
|
final ValueChanged<bool> onToggleIsUnborn;
|
||||||
final VoidCallback onRemove; // Callback pour supprimer la carte
|
final VoidCallback onRemove;
|
||||||
final bool canBeRemoved; // Pour afficher/cacher le bouton de suppression
|
final bool canBeRemoved;
|
||||||
|
|
||||||
const _ChildCardWidget({
|
const _ChildCardWidget({
|
||||||
required Key key, // Important pour le ListView.builder
|
required Key key,
|
||||||
required this.childData,
|
required this.childData,
|
||||||
required this.childIndex,
|
required this.childIndex,
|
||||||
required this.onPickImage,
|
required this.onPickImage,
|
||||||
required this.onDateSelect,
|
required this.onDateSelect,
|
||||||
|
required this.onFirstNameChanged,
|
||||||
|
required this.onLastNameChanged,
|
||||||
required this.onTogglePhotoConsent,
|
required this.onTogglePhotoConsent,
|
||||||
required this.onToggleMultipleBirth,
|
required this.onToggleMultipleBirth,
|
||||||
required this.onToggleIsUnborn,
|
required this.onToggleIsUnborn,
|
||||||
@ -367,105 +316,158 @@ class _ChildCardWidget extends StatelessWidget {
|
|||||||
required this.canBeRemoved,
|
required this.canBeRemoved,
|
||||||
}) : super(key: key);
|
}) : 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final File? currentChildImage = childData.imageFile;
|
final File? currentChildImage = widget.childData.imageFile;
|
||||||
final Color baseLavandeColor = Colors.purple.shade200;
|
// Utiliser la couleur de la carte de childData pour l'ombre si besoin, ou directement pour le fond
|
||||||
final Color initialPhotoShadow = baseLavandeColor.withAlpha(90);
|
final Color baseCardColorForShadow = widget.childData.cardColor == CardColorVertical.lavender
|
||||||
final Color hoverPhotoShadow = baseLavandeColor.withAlpha(130);
|
? 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(
|
return Container(
|
||||||
width: 300,
|
width: 345.0 * 1.1, // 379.5
|
||||||
padding: const EdgeInsets.all(20),
|
height: 570.0 * 1.2, // 684.0
|
||||||
|
padding: const EdgeInsets.all(22.0 * 1.1), // 24.2
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: const DecorationImage(image: AssetImage('assets/images/card_lavander.png'), fit: BoxFit.cover),
|
image: DecorationImage(image: AssetImage(widget.childData.cardColor.path), fit: BoxFit.cover),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20 * 1.1), // 22
|
||||||
),
|
),
|
||||||
child: Stack( // Stack pour pouvoir superposer le bouton de suppression
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
HoverReliefWidget(
|
HoverReliefWidget(
|
||||||
onPressed: onPickImage,
|
onPressed: widget.onPickImage,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
initialShadowColor: initialPhotoShadow,
|
initialShadowColor: initialPhotoShadow,
|
||||||
hoverShadowColor: hoverPhotoShadow,
|
hoverShadowColor: hoverPhotoShadow,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 100, width: 100,
|
height: 200.0,
|
||||||
|
width: 200.0,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(5.0),
|
padding: const EdgeInsets.all(5.0 * 1.1), // 5.5
|
||||||
child: currentChildImage != null
|
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),
|
: 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(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text('Enfant à naître ?', style: GoogleFonts.merienda(fontSize: 14, fontWeight: FontWeight.w600)),
|
Text('Enfant à naître ?', style: GoogleFonts.merienda(fontSize: 16 * 1.1, fontWeight: FontWeight.w600)),
|
||||||
Switch(value: childData.isUnbornChild, onChanged: onToggleIsUnborn, activeColor: Theme.of(context).primaryColor),
|
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(
|
CustomAppTextField(
|
||||||
controller: childData.firstNameController,
|
controller: _firstNameController,
|
||||||
labelText: 'Prénom',
|
labelText: 'Prénom',
|
||||||
hintText: 'Facultatif si à naître',
|
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(
|
CustomAppTextField(
|
||||||
controller: childData.lastNameController,
|
controller: _lastNameController,
|
||||||
labelText: 'Nom',
|
labelText: 'Nom',
|
||||||
hintText: 'Nom de l\'enfant',
|
hintText: 'Nom de l\'enfant',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
fieldHeight: 55.0 * 1.1, // 60.5
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 9.0 * 1.1), // 9.9
|
||||||
CustomAppTextField(
|
CustomAppTextField(
|
||||||
controller: childData.dobController,
|
controller: _dobController,
|
||||||
labelText: childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance',
|
labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance',
|
||||||
hintText: 'JJ/MM/AAAA',
|
hintText: 'JJ/MM/AAAA',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
onTap: onDateSelect,
|
onTap: widget.onDateSelect,
|
||||||
suffixIcon: Icons.calendar_today,
|
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(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
AppCustomCheckbox( // Utilisation du nouveau widget
|
AppCustomCheckbox(
|
||||||
label: 'Consentement photo',
|
label: 'Consentement photo',
|
||||||
value: childData.photoConsent,
|
value: widget.childData.photoConsent,
|
||||||
onChanged: onTogglePhotoConsent,
|
onChanged: widget.onTogglePhotoConsent,
|
||||||
|
checkboxSize: 22.0 * 1.1, // 24.2
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 6.0 * 1.1), // 6.6
|
||||||
AppCustomCheckbox( // Utilisation du nouveau widget
|
AppCustomCheckbox(
|
||||||
label: 'Naissance multiple',
|
label: 'Naissance multiple',
|
||||||
value: childData.multipleBirth,
|
value: widget.childData.multipleBirth,
|
||||||
onChanged: onToggleMultipleBirth,
|
onChanged: widget.onToggleMultipleBirth,
|
||||||
|
checkboxSize: 22.0 * 1.1, // 24.2
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (canBeRemoved) // Afficher le bouton de suppression conditionnellement
|
if (widget.canBeRemoved)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: -5, // Ajuster pour le positionnement visuel
|
top: -5, right: -5,
|
||||||
right: -5, // Ajuster pour le positionnement visuel
|
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onRemove,
|
onTap: widget.onRemove,
|
||||||
customBorder: const CircleBorder(), // Pour un effet de clic circulaire
|
customBorder: const CircleBorder(),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(color: Colors.red.withOpacity(0.8), shape: BoxShape.circle),
|
||||||
color: Colors.red.withOpacity(0.8), // Fond rouge pour le bouton X
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(Icons.close, color: Colors.white, size: 18),
|
child: const Icon(Icons.close, color: Colors.white, size: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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 '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 '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/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 {
|
class ParentRegisterStep4Screen extends StatefulWidget {
|
||||||
const ParentRegisterStep4Screen({super.key});
|
final UserRegistrationData registrationData; // Accepte les données
|
||||||
|
|
||||||
|
const ParentRegisterStep4Screen({super.key, required this.registrationData});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ParentRegisterStep4Screen> createState() => _ParentRegisterStep4ScreenState();
|
State<ParentRegisterStep4Screen> createState() => _ParentRegisterStep4ScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ParentRegisterStep4ScreenState extends State<ParentRegisterStep4Screen> {
|
class _ParentRegisterStep4ScreenState extends State<ParentRegisterStep4Screen> {
|
||||||
|
late UserRegistrationData _registrationData; // État local
|
||||||
final _motivationController = TextEditingController();
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -113,7 +125,7 @@ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lo
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text(
|
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),
|
style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@ -132,35 +144,23 @@ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lo
|
|||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Si on clique sur la zone et que ce n'est pas encore accepté,
|
if (!_cguAccepted) {
|
||||||
// ou si c'est déjà accepté (l'utilisateur veut peut-être revoir les CGU)
|
_showCGUModal();
|
||||||
_showCGUModal();
|
}
|
||||||
},
|
},
|
||||||
child: AppCustomCheckbox(
|
child: AppCustomCheckbox(
|
||||||
label: 'J\'accepte les conditions générales d\'utilisation',
|
label: 'J\'accepte les conditions générales d\'utilisation',
|
||||||
value: _cguAccepted,
|
value: _cguAccepted,
|
||||||
onChanged: (newValue) {
|
onChanged: (newValue) {
|
||||||
// La logique d'ouverture de la modale est déclenchée par le GestureDetector externe.
|
if (!_cguAccepted) {
|
||||||
// La modale mettra à jour _cguAccepted et reconstruira le widget.
|
_showCGUModal();
|
||||||
// Si newValue est true (ce qui signifie que la modale a été acceptée et _cguAccepted mis à jour),
|
} else {
|
||||||
// on n'a rien à faire de plus ici.
|
setState(() => _cguAccepted = false);
|
||||||
// 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.
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Vous pouvez ajuster checkboxSize et checkmarkSizeFactor si nécessaire
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 40),
|
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),
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||||
onPressed: _cguAccepted
|
onPressed: _cguAccepted
|
||||||
? () {
|
? () {
|
||||||
print('Motivation: ${_motivationController.text}');
|
_registrationData.updateMotivation(_motivationController.text);
|
||||||
print('CGU acceptées: $_cguAccepted');
|
_registrationData.acceptCGU();
|
||||||
|
|
||||||
// TODO: Rassembler toutes les données des étapes précédentes
|
|
||||||
final dummyData = PlaceholderRegistrationData(parent1Name: "Parent 1 Nom (Exemple)");
|
|
||||||
|
|
||||||
Navigator.pushNamed(
|
Navigator.pushNamed(
|
||||||
context,
|
context,
|
||||||
'/parent-register/step5',
|
'/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',
|
tooltip: 'Suivant',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,18 +1,192 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.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 '../../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 {
|
class ParentRegisterStep5Screen extends StatelessWidget {
|
||||||
final PlaceholderRegistrationData registrationData; // Doit maintenant utiliser la version importée
|
final UserRegistrationData registrationData;
|
||||||
|
|
||||||
const ParentRegisterStep5Screen({super.key, required this.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<Widget> 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<Widget> 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<Widget> _buildChildrenCards(BuildContext context, List<ChildData> 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<Widget> 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<Widget> 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenSize = MediaQuery.of(context).size;
|
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(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
@ -22,55 +196,49 @@ class ParentRegisterStep5Screen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 50.0),
|
padding: const EdgeInsets.symmetric(vertical: 40.0), // Padding horizontal supprimé ici
|
||||||
child: Column(
|
child: Padding( // Ajout du Padding horizontal externe
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
padding: EdgeInsets.symmetric(horizontal: screenSize.width / 4.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
child: Column(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
'Étape 5/5',
|
children: [
|
||||||
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
|
Text('Étape 5/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
|
||||||
),
|
const SizedBox(height: 20),
|
||||||
const SizedBox(height: 20),
|
Text('Récapitulatif de votre demande', style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), textAlign: TextAlign.center),
|
||||||
Text(
|
const SizedBox(height: 30),
|
||||||
'Récapitulatif de votre demande',
|
|
||||||
style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87),
|
_buildParent1Card(context, registrationData.parent1),
|
||||||
textAlign: TextAlign.center,
|
const SizedBox(height: 20),
|
||||||
),
|
if (registrationData.parent2 != null) ...[
|
||||||
const SizedBox(height: 30),
|
_buildParent2Card(context, registrationData.parent2!),
|
||||||
|
const SizedBox(height: 20),
|
||||||
// TODO: Construire les cartes récapitulatives ici
|
],
|
||||||
// _buildParent1Card(context, registrationData),
|
..._buildChildrenCards(context, registrationData.children),
|
||||||
// if (registrationData.parent2Exists) _buildParent2Card(context, registrationData),
|
_buildMotivationCard(context, registrationData.motivationText),
|
||||||
// ..._buildChildrenCards(context, registrationData),
|
const SizedBox(height: 40),
|
||||||
// _buildMotivationCard(context, registrationData),
|
ImageButton(
|
||||||
|
bg: 'assets/images/btn_green.png',
|
||||||
const SizedBox(height: 40),
|
text: 'Soumettre ma demande',
|
||||||
// Utilisation du ImageButton
|
textColor: const Color(0xFF2D6A4F),
|
||||||
ImageButton(
|
width: 350,
|
||||||
bg: 'assets/images/btn_green.png',
|
height: 50,
|
||||||
text: 'Soumettre ma demande',
|
fontSize: 18,
|
||||||
textColor: const Color(0xFF2D6A4F),
|
onPressed: () {
|
||||||
width: 350,
|
print("Données finales: ${registrationData.parent1.firstName}, Enfant(s): ${registrationData.children.length}");
|
||||||
height: 50,
|
_showConfirmationModal(context);
|
||||||
fontSize: 18,
|
},
|
||||||
onPressed: () {
|
),
|
||||||
_showConfirmationModal(context);
|
],
|
||||||
},
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Chevrons de navigation (uniquement retour vers étape 4)
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: screenSize.height / 2 - 20,
|
top: screenSize.height / 2 - 20,
|
||||||
left: 40,
|
left: 40,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Transform.flip(
|
icon: Transform.flip(flipX: true, child: Image.asset('assets/images/chevron_right.png', height: 40)),
|
||||||
flipX: true,
|
|
||||||
child: Image.asset('assets/images/chevron_right.png', height: 40)
|
|
||||||
),
|
|
||||||
onPressed: () => Navigator.pop(context), // Retour à l'étape 4
|
onPressed: () => Navigator.pop(context), // Retour à l'étape 4
|
||||||
tooltip: 'Retour',
|
tooltip: 'Retour',
|
||||||
),
|
),
|
||||||
@ -108,10 +276,64 @@ class ParentRegisterStep5Screen extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Méthodes pour construire les cartes individuelles
|
// Widget générique _SummaryCard (ajusté)
|
||||||
// Widget _buildParent1Card(BuildContext context, PlaceholderRegistrationData data) { ... }
|
class _SummaryCard extends StatelessWidget {
|
||||||
// Widget _buildParent2Card(BuildContext context, PlaceholderRegistrationData data) { ... }
|
final String backgroundImagePath;
|
||||||
// List<Widget> _buildChildrenCards(BuildContext context, PlaceholderRegistrationData data) { ... }
|
final String title;
|
||||||
// Widget _buildMotivationCard(BuildContext context, PlaceholderRegistrationData data) { ... }
|
final List<Widget> 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',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'dart:math' as math; // Pour la rotation du chevron
|
import 'dart:math' as math; // Pour la rotation du chevron
|
||||||
import '../../widgets/hover_relief_widget.dart'; // Import du widget générique
|
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 {
|
class RegisterChoiceScreen extends StatelessWidget {
|
||||||
const RegisterChoiceScreen({super.key});
|
const RegisterChoiceScreen({super.key});
|
||||||
@ -73,9 +74,9 @@ class RegisterChoiceScreen extends StatelessWidget {
|
|||||||
aspectRatio: 2 / 3,
|
aspectRatio: 2 / 3,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: AssetImage('assets/images/card_rose.png'),
|
image: AssetImage(CardColorVertical.pink.path),
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
65
frontend/lib/utils/data_generator.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
class DataGenerator {
|
||||||
|
static final Random _random = Random();
|
||||||
|
|
||||||
|
static final List<String> _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<String> _lastNames = [
|
||||||
|
'Martin', 'Bernard', 'Dubois', 'Thomas', 'Robert', 'Richard', 'Petit', 'Durand', 'Leroy', 'Moreau',
|
||||||
|
'Simon', 'Laurent', 'Lefebvre', 'Michel', 'Garcia', 'David', 'Bertrand', 'Roux', 'Vincent', 'Fournier'
|
||||||
|
];
|
||||||
|
|
||||||
|
static final List<String> _addressSuffixes = [
|
||||||
|
'Rue de la Paix', 'Boulevard des Rêves', 'Avenue du Soleil', 'Place des Étoiles', 'Chemin des Champs'
|
||||||
|
];
|
||||||
|
|
||||||
|
static final List<String> _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<String> chosenSnippets = [];
|
||||||
|
while(chosenSnippets.length < count) {
|
||||||
|
String snippet = _motivationSnippets[_random.nextInt(_motivationSnippets.length)];
|
||||||
|
if (!chosenSnippets.contains(snippet)) {
|
||||||
|
chosenSnippets.add(snippet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chosenSnippets.join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -51,7 +51,7 @@ class AppCustomCheckbox extends StatelessWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
style: GoogleFonts.merienda(fontSize: 14),
|
style: GoogleFonts.merienda(fontSize: 16),
|
||||||
overflow: TextOverflow.ellipsis, // Gérer le texte long
|
overflow: TextOverflow.ellipsis, // Gérer le texte long
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -12,8 +12,8 @@ class CustomAppTextField extends StatefulWidget {
|
|||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final String labelText;
|
final String labelText;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
final double fieldHeight;
|
|
||||||
final double fieldWidth;
|
final double fieldWidth;
|
||||||
|
final double fieldHeight;
|
||||||
final bool obscureText;
|
final bool obscureText;
|
||||||
final TextInputType keyboardType;
|
final TextInputType keyboardType;
|
||||||
final String? Function(String?)? validator;
|
final String? Function(String?)? validator;
|
||||||
@ -23,14 +23,16 @@ class CustomAppTextField extends StatefulWidget {
|
|||||||
final bool readOnly;
|
final bool readOnly;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final IconData? suffixIcon;
|
final IconData? suffixIcon;
|
||||||
|
final double labelFontSize;
|
||||||
|
final double inputFontSize;
|
||||||
|
|
||||||
const CustomAppTextField({
|
const CustomAppTextField({
|
||||||
super.key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.labelText,
|
required this.labelText,
|
||||||
this.hintText = '',
|
this.hintText = '',
|
||||||
this.fieldHeight = 50.0,
|
|
||||||
this.fieldWidth = 300.0,
|
this.fieldWidth = 300.0,
|
||||||
|
this.fieldHeight = 53.0,
|
||||||
this.obscureText = false,
|
this.obscureText = false,
|
||||||
this.keyboardType = TextInputType.text,
|
this.keyboardType = TextInputType.text,
|
||||||
this.validator,
|
this.validator,
|
||||||
@ -40,6 +42,8 @@ class CustomAppTextField extends StatefulWidget {
|
|||||||
this.readOnly = false,
|
this.readOnly = false,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.suffixIcon,
|
this.suffixIcon,
|
||||||
|
this.labelFontSize = 18.0,
|
||||||
|
this.inputFontSize = 18.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -61,6 +65,10 @@ class _CustomAppTextFieldState extends State<CustomAppTextField> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
const double fontHeightMultiplier = 1.2;
|
||||||
|
const double internalVerticalPadding = 16.0;
|
||||||
|
final double dynamicFieldHeight = widget.fieldHeight;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -68,7 +76,7 @@ class _CustomAppTextFieldState extends State<CustomAppTextField> {
|
|||||||
Text(
|
Text(
|
||||||
widget.labelText,
|
widget.labelText,
|
||||||
style: GoogleFonts.merienda(
|
style: GoogleFonts.merienda(
|
||||||
fontSize: 14,
|
fontSize: widget.labelFontSize,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@ -76,7 +84,7 @@ class _CustomAppTextFieldState extends State<CustomAppTextField> {
|
|||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: widget.fieldWidth,
|
width: widget.fieldWidth,
|
||||||
height: widget.fieldHeight,
|
height: dynamicFieldHeight,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
children: [
|
children: [
|
||||||
@ -87,7 +95,7 @@ class _CustomAppTextFieldState extends State<CustomAppTextField> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
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(
|
child: TextFormField(
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
obscureText: widget.obscureText,
|
obscureText: widget.obscureText,
|
||||||
@ -95,7 +103,10 @@ class _CustomAppTextFieldState extends State<CustomAppTextField> {
|
|||||||
enabled: widget.enabled,
|
enabled: widget.enabled,
|
||||||
readOnly: widget.readOnly,
|
readOnly: widget.readOnly,
|
||||||
onTap: widget.onTap,
|
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 ??
|
validator: widget.validator ??
|
||||||
(value) {
|
(value) {
|
||||||
if (!widget.enabled || widget.readOnly) return null;
|
if (!widget.enabled || widget.readOnly) return null;
|
||||||
@ -106,13 +117,13 @@ class _CustomAppTextFieldState extends State<CustomAppTextField> {
|
|||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: widget.hintText,
|
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,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
suffixIcon: widget.suffixIcon != null
|
suffixIcon: widget.suffixIcon != null
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(right: 0.0),
|
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,
|
: null,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
|
|||||||
@ -30,6 +30,7 @@ flutter:
|
|||||||
|
|
||||||
assets:
|
assets:
|
||||||
- assets/images/ # Déclarer le dossier entier
|
- assets/images/ # Déclarer le dossier entier
|
||||||
|
- assets/cards/ # Nouveau dossier de cartes
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
- family: Merienda
|
- family: Merienda
|
||||||
|
|||||||
1
lib/screens/auth/login_screen.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
22
lib/screens/auth/parent_register_step3_screen.dart
Normal file
@ -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,
|
||||||
|
),
|
||||||