Implémentation du parcours d'inscription des assistantes maternelles en 4 étapes + écran de confirmation, en utilisant Provider pour la gestion d'état. Fonctionnalités implémentées : - Étape 1 : Identité (nom, prénom, adresse, email, mot de passe) - Étape 2 : Infos professionnelles (photo, agrément, NIR, capacité d'accueil) - Étape 3 : Présentation personnelle et acceptation CGU - Étape 4 : Récapitulatif et validation finale - Écran de confirmation post-inscription Fichiers ajoutés : - models/nanny_registration_data.dart : Modèle de données avec Provider - screens/auth/nanny_register_step1_screen.dart : Identité - screens/auth/nanny_register_step2_screen.dart : Infos pro - screens/auth/nanny_register_step3_screen.dart : Présentation - screens/auth/nanny_register_step4_screen.dart : Récapitulatif - screens/auth/nanny_register_confirmation_screen.dart : Confirmation - screens/unknown_screen.dart : Écran pour routes inconnues - config/app_router.dart : Copie du routeur (à intégrer) Refs: #40 (Panneau 1 Identité), #41 (Panneau 2 Infos pro), #42 (Finalisation)
339 lines
16 KiB
Dart
339 lines
16 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'dart:math' as math;
|
|
import 'dart:io'; // Pour FileImage si _pickPhoto utilise un File
|
|
|
|
import '../../../models/nanny_registration_data.dart';
|
|
import '../../../widgets/custom_app_text_field.dart';
|
|
import '../../../widgets/app_custom_checkbox.dart'; // Import de la checkbox
|
|
import '../../../widgets/hover_relief_widget.dart'; // Import du HoverReliefWidget
|
|
import '../../../models/card_assets.dart';
|
|
// import '../../../utils/data_generator.dart'; // Plus besoin pour l'initialisation directe ici
|
|
|
|
class NannyRegisterStep2Screen extends StatefulWidget {
|
|
const NannyRegisterStep2Screen({super.key});
|
|
|
|
@override
|
|
State<NannyRegisterStep2Screen> createState() => _NannyRegisterStep2ScreenState();
|
|
}
|
|
|
|
class _NannyRegisterStep2ScreenState extends State<NannyRegisterStep2Screen> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
final _dateOfBirthController = TextEditingController();
|
|
// final _placeOfBirthController = TextEditingController(); // Remplacé
|
|
final _birthCityController = TextEditingController(); // Nouveau
|
|
final _birthCountryController = TextEditingController(); // Nouveau
|
|
final _nirController = TextEditingController();
|
|
final _agrementController = TextEditingController();
|
|
final _capacityController = TextEditingController();
|
|
DateTime? _selectedDate;
|
|
String? _photoPathFramework; // Pour stocker le chemin de la photo (Asset ou File path)
|
|
File? _photoFile; // Pour stocker le fichier image si sélectionné localement
|
|
bool _photoConsent = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final data = Provider.of<NannyRegistrationData>(context, listen: false);
|
|
_selectedDate = data.dateOfBirth;
|
|
_dateOfBirthController.text = data.dateOfBirth != null ? DateFormat('dd/MM/yyyy').format(data.dateOfBirth!) : '';
|
|
_birthCityController.text = data.birthCity;
|
|
_birthCountryController.text = data.birthCountry;
|
|
_nirController.text = data.nir;
|
|
_agrementController.text = data.agrementNumber;
|
|
_capacityController.text = data.capacity?.toString() ?? '';
|
|
// Gérer la photo existante (pourrait être un path d'asset ou un path de fichier)
|
|
if (data.photoPath != null) {
|
|
if (data.photoPath!.startsWith('assets/')) {
|
|
_photoPathFramework = data.photoPath;
|
|
_photoFile = null;
|
|
} else {
|
|
_photoFile = File(data.photoPath!);
|
|
_photoPathFramework = data.photoPath; // ou _photoFile.path
|
|
}
|
|
}
|
|
_photoConsent = data.photoConsent;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_dateOfBirthController.dispose();
|
|
_birthCityController.dispose();
|
|
_birthCountryController.dispose();
|
|
_nirController.dispose();
|
|
_agrementController.dispose();
|
|
_capacityController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _selectDate(BuildContext context) async {
|
|
final DateTime? picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: _selectedDate ?? DateTime.now().subtract(const Duration(days: 365 * 25)), // Default à 25 ans si null
|
|
firstDate: DateTime(1920, 1),
|
|
lastDate: DateTime.now().subtract(const Duration(days: 365 * 18)), // Assurer un âge minimum de 18 ans
|
|
locale: const Locale('fr', 'FR'),
|
|
);
|
|
if (picked != null && picked != _selectedDate) {
|
|
setState(() {
|
|
_selectedDate = picked;
|
|
_dateOfBirthController.text = DateFormat('dd/MM/yyyy').format(picked);
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _pickPhoto() async {
|
|
// TODO: Remplacer par la vraie logique ImagePicker
|
|
// final imagePicker = ImagePicker();
|
|
// final pickedFile = await imagePicker.pickImage(source: ImageSource.gallery);
|
|
// if (pickedFile != null) {
|
|
// setState(() {
|
|
// _photoFile = File(pickedFile.path);
|
|
// _photoPathFramework = pickedFile.path; // pour la sauvegarde
|
|
// });
|
|
// } else {
|
|
// // Simuler la sélection d'un asset pour test si aucun fichier n'est choisi
|
|
setState(() {
|
|
_photoPathFramework = 'assets/images/icon_assmat.png'; // Simule une photo asset
|
|
_photoFile = null; // Assurez-vous que _photoFile est null si c'est un asset
|
|
});
|
|
// }
|
|
print("Photo sélectionnée: $_photoPathFramework");
|
|
}
|
|
|
|
void _submitForm() {
|
|
if (_formKey.currentState!.validate()) {
|
|
if (_photoPathFramework != null && !_photoConsent) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Veuillez accepter le consentement photo pour continuer.')),
|
|
);
|
|
return;
|
|
}
|
|
|
|
Provider.of<NannyRegistrationData>(context, listen: false)
|
|
.updateProfessionalInfo(
|
|
photoPath: _photoPathFramework, // Sauvegarder le chemin (asset ou fichier)
|
|
photoConsent: _photoConsent,
|
|
dateOfBirth: _selectedDate,
|
|
birthCity: _birthCityController.text,
|
|
birthCountry: _birthCountryController.text,
|
|
nir: _nirController.text,
|
|
agrementNumber: _agrementController.text,
|
|
capacity: int.tryParse(_capacityController.text)
|
|
);
|
|
context.go('/nanny-register-step3');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final screenSize = MediaQuery.of(context).size;
|
|
const cardColor = CardColorHorizontal.green; // Couleur de la carte
|
|
final Color baseCardColorForShadow = Colors.green.shade300;
|
|
final Color initialPhotoShadow = baseCardColorForShadow.withAlpha(90);
|
|
final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130);
|
|
|
|
ImageProvider? currentImageProvider;
|
|
if (_photoFile != null) {
|
|
currentImageProvider = FileImage(_photoFile!);
|
|
} else if (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')) {
|
|
currentImageProvider = AssetImage(_photoPathFramework!);
|
|
}
|
|
|
|
return Scaffold(
|
|
body: Stack(
|
|
children: [
|
|
Positioned.fill(
|
|
child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat),
|
|
),
|
|
Center(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(vertical: 40.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text('Étape 2/4', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
|
|
const SizedBox(height: 10),
|
|
Container(
|
|
width: screenSize.width * 0.7, // Largeur de la carte
|
|
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
|
|
constraints: const BoxConstraints(minHeight: 650), // Hauteur minimale ajustée
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(image: AssetImage(cardColor.path), fit: BoxFit.fill),
|
|
),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'Vos informations professionnelles',
|
|
style: GoogleFonts.merienda(
|
|
fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87, // Couleur du titre ajustée
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 30),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Colonne Gauche: Photo et Checkbox
|
|
SizedBox(
|
|
width: 300, // Largeur fixe pour la colonne photo (200 * 1.5)
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center, // Centrer les éléments horizontalement
|
|
children: [
|
|
HoverReliefWidget(
|
|
onPressed: _pickPhoto,
|
|
borderRadius: BorderRadius.circular(10.0),
|
|
initialShadowColor: initialPhotoShadow,
|
|
hoverShadowColor: hoverPhotoShadow,
|
|
child: SizedBox(
|
|
height: 270, // (180 * 1.5)
|
|
width: 270, // (180 * 1.5)
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
image: currentImageProvider != null
|
|
? DecorationImage(image: currentImageProvider, fit: BoxFit.cover)
|
|
: null,
|
|
),
|
|
child: currentImageProvider == null
|
|
? Image.asset('assets/images/photo.png', fit: BoxFit.contain)
|
|
: null,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 10), // Espace réduit
|
|
AppCustomCheckbox(
|
|
label: 'J\'accepte l\'utilisation de ma photo.',
|
|
value: _photoConsent,
|
|
onChanged: (val) => setState(() => _photoConsent = val ?? false),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 30), // Augmenter l'espace entre les colonnes
|
|
// Colonne Droite: Champs de naissance
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
CustomAppTextField(
|
|
controller: _birthCityController,
|
|
labelText: 'Ville de naissance',
|
|
hintText: 'Votre ville de naissance',
|
|
fieldWidth: double.infinity,
|
|
validator: (v) => v!.isEmpty ? 'Ville requise' : null,
|
|
),
|
|
const SizedBox(height: 20),
|
|
CustomAppTextField(
|
|
controller: _birthCountryController,
|
|
labelText: 'Pays de naissance',
|
|
hintText: 'Votre pays de naissance',
|
|
fieldWidth: double.infinity,
|
|
validator: (v) => v!.isEmpty ? 'Pays requis' : null,
|
|
),
|
|
const SizedBox(height: 20),
|
|
CustomAppTextField(
|
|
controller: _dateOfBirthController,
|
|
labelText: 'Date de naissance',
|
|
hintText: 'JJ/MM/AAAA',
|
|
readOnly: true,
|
|
onTap: () => _selectDate(context),
|
|
suffixIcon: Icons.calendar_today, // Assurez-vous que CustomAppTextField gère suffixIcon
|
|
fieldWidth: double.infinity,
|
|
validator: (v) => _selectedDate == null ? 'Date requise' : null,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
CustomAppTextField(
|
|
controller: _nirController,
|
|
labelText: 'N° Sécurité Sociale (NIR)',
|
|
hintText: 'Votre NIR à 13 chiffres',
|
|
keyboardType: TextInputType.number,
|
|
fieldWidth: double.infinity,
|
|
validator: (v) { // Validation plus précise du NIR
|
|
if (v == null || v.isEmpty) return 'NIR requis';
|
|
if (v.length != 13) return 'Le NIR doit contenir 13 chiffres';
|
|
if (!RegExp(r'^[1-3]').hasMatch(v[0])) return 'Le NIR doit commencer par 1, 2 ou 3';
|
|
// D'autres validations plus complexes (clé de contrôle) peuvent être ajoutées
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: CustomAppTextField(
|
|
controller: _agrementController,
|
|
labelText: 'N° d\'agrément',
|
|
hintText: 'Votre numéro d\'agrément',
|
|
fieldWidth: double.infinity,
|
|
validator: (v) => v!.isEmpty ? 'Agrément requis' : null,
|
|
),
|
|
),
|
|
const SizedBox(width: 20),
|
|
Expanded(
|
|
child: CustomAppTextField(
|
|
controller: _capacityController,
|
|
labelText: 'Capacité d\'accueil',
|
|
hintText: 'Ex: 3',
|
|
keyboardType: TextInputType.number,
|
|
fieldWidth: double.infinity,
|
|
validator: (v) {
|
|
if (v == null || v.isEmpty) return 'Capacité requise';
|
|
final n = int.tryParse(v);
|
|
if (n == null || n <= 0) return 'Nombre invalide';
|
|
return null;
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
// Chevron Gauche (Retour)
|
|
Positioned(
|
|
top: screenSize.height / 2 - 20,
|
|
left: 40,
|
|
child: IconButton(
|
|
icon: Transform(alignment: Alignment.center, transform: Matrix4.rotationY(math.pi), child: Image.asset('assets/images/chevron_right.png', height: 40)),
|
|
onPressed: () {
|
|
if (context.canPop()) {
|
|
context.pop();
|
|
} else {
|
|
context.go('/nanny-register-step1');
|
|
}
|
|
},
|
|
tooltip: 'Précédent',
|
|
),
|
|
),
|
|
// Chevron Droit (Suivant)
|
|
Positioned(
|
|
top: screenSize.height / 2 - 20,
|
|
right: 40,
|
|
child: IconButton(
|
|
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
|
onPressed: _submitForm,
|
|
tooltip: 'Suivant',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |