feat(#78): Migrer ParentRegisterStep3Screen (Enfants) vers infrastructure multi-modes
Adaptation responsive du formulaire "Informations Enfants" (Parent Step 3) : - Desktop : Conservation du layout horizontal avec scroll et effets de fondu - Mobile : Layout vertical avec cartes empilées - Header fixe - Bouton "+" carré (50px) centré à la fin de la liste - Boutons navigation intégrés au scroll - Cartes enfants adaptées (scale 0.9, polices réduites) - Mise à jour DisplayConfig (mode optionnel par défaut) - Mise à jour AppCustomCheckbox (paramètre fontSize) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
bdecbc2c1d
commit
eea94769bf
@ -25,7 +25,7 @@ class DisplayConfig {
|
||||
/// Crée une config à partir du contexte
|
||||
factory DisplayConfig.fromContext(
|
||||
BuildContext context, {
|
||||
required DisplayMode mode,
|
||||
DisplayMode mode = DisplayMode.editable,
|
||||
}) {
|
||||
return DisplayConfig(
|
||||
mode: mode,
|
||||
|
||||
@ -5,9 +5,11 @@ import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io' show File;
|
||||
import '../../widgets/hover_relief_widget.dart';
|
||||
import '../../widgets/child_card_widget.dart';
|
||||
import '../../widgets/custom_navigation_button.dart';
|
||||
import '../../models/user_registration_data.dart';
|
||||
import '../../utils/data_generator.dart';
|
||||
import '../../models/card_assets.dart';
|
||||
import '../../config/display_config.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@ -204,14 +206,157 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
Widget build(BuildContext context) {
|
||||
final registrationData = Provider.of<UserRegistrationData>(context /*, listen: true par défaut */);
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
final config = DisplayConfig.fromContext(context, mode: DisplayMode.editable);
|
||||
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat),
|
||||
),
|
||||
Center(
|
||||
config.isMobile
|
||||
? _buildMobileLayout(context, config, screenSize, registrationData)
|
||||
: _buildDesktopLayout(context, config, screenSize, registrationData),
|
||||
// Chevrons desktop uniquement
|
||||
if (!config.isMobile) ...[
|
||||
Positioned(
|
||||
top: screenSize.height / 2 - 20,
|
||||
left: 40,
|
||||
child: IconButton(
|
||||
icon: Transform(alignment: Alignment.center, transform: Matrix4.rotationY(math.pi), child: Image.asset('assets/images/chevron_right.png', height: 40)),
|
||||
onPressed: () {
|
||||
if (context.canPop()) {
|
||||
context.pop();
|
||||
} else {
|
||||
context.go('/parent-register-step2');
|
||||
}
|
||||
},
|
||||
tooltip: 'Retour',
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: screenSize.height / 2 - 20,
|
||||
right: 40,
|
||||
child: IconButton(
|
||||
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||
onPressed: () {
|
||||
context.go('/parent-register-step4');
|
||||
},
|
||||
tooltip: 'Suivant',
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Layout MOBILE : Cartes empilées verticalement
|
||||
Widget _buildMobileLayout(BuildContext context, DisplayConfig config, Size screenSize, UserRegistrationData registrationData) {
|
||||
return Column(
|
||||
children: [
|
||||
// Header fixe
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Étape 3/5',
|
||||
style: GoogleFonts.merienda(fontSize: 13, color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Informations Enfants',
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Liste scrollable des cartes + boutons
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.05),
|
||||
child: Column(
|
||||
children: [
|
||||
// Générer les cartes enfants
|
||||
for (int index = 0; index < registrationData.children.length; index++) ...[
|
||||
ChildCardWidget(
|
||||
key: ValueKey(registrationData.children[index].hashCode),
|
||||
childData: registrationData.children[index],
|
||||
childIndex: index,
|
||||
onPickImage: () => _pickImage(index, registrationData),
|
||||
onDateSelect: () => _selectDate(context, index, registrationData),
|
||||
onFirstNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData(
|
||||
firstName: value, lastName: registrationData.children[index].lastName, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent,
|
||||
multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor
|
||||
))),
|
||||
onLastNameChanged: (value) => setState(() => registrationData.updateChild(index, ChildData(
|
||||
firstName: registrationData.children[index].firstName, lastName: value, dob: registrationData.children[index].dob, photoConsent: registrationData.children[index].photoConsent,
|
||||
multipleBirth: registrationData.children[index].multipleBirth, isUnbornChild: registrationData.children[index].isUnbornChild, imageFile: registrationData.children[index].imageFile, cardColor: registrationData.children[index].cardColor
|
||||
))),
|
||||
onTogglePhotoConsent: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: newValue,
|
||||
multipleBirth: oldChild.multipleBirth, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
},
|
||||
onToggleMultipleBirth: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: oldChild.dob, photoConsent: oldChild.photoConsent,
|
||||
multipleBirth: newValue, isUnbornChild: oldChild.isUnbornChild, imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
},
|
||||
onToggleIsUnborn: (newValue) {
|
||||
final oldChild = registrationData.children[index];
|
||||
registrationData.updateChild(index, ChildData(
|
||||
firstName: oldChild.firstName, lastName: oldChild.lastName, dob: DataGenerator.dob(isUnborn: newValue),
|
||||
photoConsent: oldChild.photoConsent, multipleBirth: oldChild.multipleBirth, isUnbornChild: newValue,
|
||||
imageFile: oldChild.imageFile, cardColor: oldChild.cardColor
|
||||
));
|
||||
},
|
||||
onRemove: () => _removeChild(index, registrationData),
|
||||
canBeRemoved: registrationData.children.length > 1,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Bouton "+" carré à la fin de la liste
|
||||
Center(
|
||||
child: HoverReliefWidget(
|
||||
onPressed: () => _addChild(registrationData),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
child: Image.asset('assets/images/plus.png', fit: BoxFit.contain),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
// Boutons navigation en bas du scroll
|
||||
_buildMobileButtons(context, config, screenSize),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Layout DESKTOP : Scroll horizontal avec fondu
|
||||
Widget _buildDesktopLayout(BuildContext context, DisplayConfig config, Size screenSize, UserRegistrationData registrationData) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Étape 3/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
|
||||
@ -306,13 +451,18 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Chevrons de navigation
|
||||
Positioned(
|
||||
top: screenSize.height / 2 - 20,
|
||||
left: 40,
|
||||
child: IconButton(
|
||||
icon: Transform(alignment: Alignment.center, transform: Matrix4.rotationY(math.pi), child: Image.asset('assets/images/chevron_right.png', height: 40)),
|
||||
);
|
||||
}
|
||||
|
||||
/// Boutons navigation mobile
|
||||
Widget _buildMobileButtons(BuildContext context, DisplayConfig config, Size screenSize) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: HoverReliefWidget(
|
||||
child: CustomNavigationButton(
|
||||
text: 'Précédent',
|
||||
style: NavigationButtonStyle.purple,
|
||||
onPressed: () {
|
||||
if (context.canPop()) {
|
||||
context.pop();
|
||||
@ -320,22 +470,28 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
|
||||
context.go('/parent-register-step2');
|
||||
}
|
||||
},
|
||||
tooltip: 'Retour',
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: screenSize.height / 2 - 20,
|
||||
right: 40,
|
||||
child: IconButton(
|
||||
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: HoverReliefWidget(
|
||||
child: CustomNavigationButton(
|
||||
text: 'Suivant',
|
||||
style: NavigationButtonStyle.green,
|
||||
onPressed: () {
|
||||
context.go('/parent-register-step4');
|
||||
},
|
||||
tooltip: 'Suivant',
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ class AppCustomCheckbox extends StatelessWidget {
|
||||
final ValueChanged<bool> onChanged;
|
||||
final double checkboxSize;
|
||||
final double checkmarkSizeFactor;
|
||||
final double fontSize;
|
||||
|
||||
const AppCustomCheckbox({
|
||||
super.key,
|
||||
@ -15,6 +16,7 @@ class AppCustomCheckbox extends StatelessWidget {
|
||||
required this.onChanged,
|
||||
this.checkboxSize = 20.0,
|
||||
this.checkmarkSizeFactor = 1.4,
|
||||
this.fontSize = 16.0,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -51,7 +53,7 @@ class AppCustomCheckbox extends StatelessWidget {
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: GoogleFonts.merienda(fontSize: 16),
|
||||
style: GoogleFonts.merienda(fontSize: fontSize),
|
||||
overflow: TextOverflow.ellipsis, // Gérer le texte long
|
||||
),
|
||||
),
|
||||
|
||||
@ -7,6 +7,7 @@ import '../models/card_assets.dart';
|
||||
import 'custom_app_text_field.dart';
|
||||
import 'app_custom_checkbox.dart';
|
||||
import 'hover_relief_widget.dart';
|
||||
import '../config/display_config.dart';
|
||||
|
||||
/// Widget pour afficher et éditer une carte enfant
|
||||
/// Utilisé dans le workflow d'inscription des parents
|
||||
@ -87,6 +88,9 @@ class _ChildCardWidgetState extends State<ChildCardWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final config = DisplayConfig.fromContext(context);
|
||||
final scaleFactor = config.isMobile ? 0.9 : 1.1; // Réduire légèrement sur mobile
|
||||
|
||||
final File? currentChildImage = widget.childData.imageFile;
|
||||
// Utiliser la couleur de la carte de childData pour l'ombre si besoin, ou directement pour le fond
|
||||
final Color baseCardColorForShadow = widget.childData.cardColor == CardColorVertical.lavender
|
||||
@ -96,12 +100,12 @@ class _ChildCardWidgetState extends State<ChildCardWidget> {
|
||||
final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130);
|
||||
|
||||
return Container(
|
||||
width: 345.0 * 1.1, // 379.5
|
||||
height: 570.0 * 1.2, // 684.0
|
||||
padding: const EdgeInsets.all(22.0 * 1.1), // 24.2
|
||||
width: 345.0 * scaleFactor,
|
||||
height: config.isMobile ? null : 570.0 * scaleFactor, // Hauteur auto sur mobile
|
||||
padding: EdgeInsets.all(22.0 * scaleFactor),
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(image: AssetImage(widget.childData.cardColor.path), fit: BoxFit.cover),
|
||||
borderRadius: BorderRadius.circular(20 * 1.1), // 22
|
||||
image: DecorationImage(image: AssetImage(widget.childData.cardColor.path), fit: BoxFit.fill),
|
||||
borderRadius: BorderRadius.circular(20 * scaleFactor),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
@ -114,43 +118,56 @@ class _ChildCardWidgetState extends State<ChildCardWidget> {
|
||||
initialShadowColor: initialPhotoShadow,
|
||||
hoverShadowColor: hoverPhotoShadow,
|
||||
child: SizedBox(
|
||||
height: 200.0,
|
||||
width: 200.0,
|
||||
height: 200.0 * (config.isMobile ? 0.8 : 1.0),
|
||||
width: 200.0 * (config.isMobile ? 0.8 : 1.0),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5.0 * 1.1), // 5.5
|
||||
padding: EdgeInsets.all(5.0 * scaleFactor),
|
||||
child: currentChildImage != null
|
||||
? ClipRRect(borderRadius: BorderRadius.circular(10 * 1.1), child: kIsWeb ? Image.network(currentChildImage.path, fit: BoxFit.cover) : Image.file(currentChildImage, fit: BoxFit.cover))
|
||||
? ClipRRect(borderRadius: BorderRadius.circular(10 * scaleFactor), child: kIsWeb ? Image.network(currentChildImage.path, fit: BoxFit.cover) : Image.file(currentChildImage, fit: BoxFit.cover))
|
||||
: Image.asset('assets/images/photo.png', fit: BoxFit.contain),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12.0 * 1.1), // Augmenté pour plus d'espace après la photo
|
||||
SizedBox(height: 12.0 * scaleFactor),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Enfant à naître ?', style: GoogleFonts.merienda(fontSize: 16 * 1.1, fontWeight: FontWeight.w600)),
|
||||
Switch(value: widget.childData.isUnbornChild, onChanged: widget.onToggleIsUnborn, activeColor: Theme.of(context).primaryColor),
|
||||
Text(
|
||||
'Enfant à naître ?',
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: config.isMobile ? 14 : 16 * scaleFactor,
|
||||
fontWeight: FontWeight.w600
|
||||
)
|
||||
),
|
||||
Transform.scale(
|
||||
scale: config.isMobile ? 0.8 : 1.0,
|
||||
child: Switch(value: widget.childData.isUnbornChild, onChanged: widget.onToggleIsUnborn, activeColor: Theme.of(context).primaryColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 9.0 * 1.1), // 9.9
|
||||
SizedBox(height: 9.0 * scaleFactor),
|
||||
CustomAppTextField(
|
||||
controller: _firstNameController,
|
||||
labelText: 'Prénom',
|
||||
hintText: 'Facultatif si à naître',
|
||||
isRequired: !widget.childData.isUnbornChild,
|
||||
fieldHeight: 55.0 * 1.1, // 60.5
|
||||
fieldHeight: config.isMobile ? 45.0 : 55.0 * scaleFactor,
|
||||
labelFontSize: config.isMobile ? 14.0 : 22.0, // Police réduite mobile
|
||||
inputFontSize: config.isMobile ? 14.0 : 20.0,
|
||||
),
|
||||
const SizedBox(height: 6.0 * 1.1), // 6.6
|
||||
SizedBox(height: 6.0 * scaleFactor),
|
||||
CustomAppTextField(
|
||||
controller: _lastNameController,
|
||||
labelText: 'Nom',
|
||||
hintText: 'Nom de l\'enfant',
|
||||
enabled: true,
|
||||
fieldHeight: 55.0 * 1.1, // 60.5
|
||||
fieldHeight: config.isMobile ? 45.0 : 55.0 * scaleFactor,
|
||||
labelFontSize: config.isMobile ? 14.0 : 22.0,
|
||||
inputFontSize: config.isMobile ? 14.0 : 20.0,
|
||||
),
|
||||
const SizedBox(height: 9.0 * 1.1), // 9.9
|
||||
SizedBox(height: 9.0 * scaleFactor),
|
||||
CustomAppTextField(
|
||||
controller: _dobController,
|
||||
labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance',
|
||||
@ -158,9 +175,11 @@ class _ChildCardWidgetState extends State<ChildCardWidget> {
|
||||
readOnly: true,
|
||||
onTap: widget.onDateSelect,
|
||||
suffixIcon: Icons.calendar_today,
|
||||
fieldHeight: 55.0 * 1.1, // 60.5
|
||||
fieldHeight: config.isMobile ? 45.0 : 55.0 * scaleFactor,
|
||||
labelFontSize: config.isMobile ? 14.0 : 22.0,
|
||||
inputFontSize: config.isMobile ? 14.0 : 20.0,
|
||||
),
|
||||
const SizedBox(height: 11.0 * 1.1), // 12.1
|
||||
SizedBox(height: 11.0 * scaleFactor),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -168,14 +187,16 @@ class _ChildCardWidgetState extends State<ChildCardWidget> {
|
||||
label: 'Consentement photo',
|
||||
value: widget.childData.photoConsent,
|
||||
onChanged: widget.onTogglePhotoConsent,
|
||||
checkboxSize: 22.0 * 1.1, // 24.2
|
||||
checkboxSize: config.isMobile ? 20.0 : 22.0 * scaleFactor,
|
||||
fontSize: config.isMobile ? 13.0 : 16.0,
|
||||
),
|
||||
const SizedBox(height: 6.0 * 1.1), // 6.6
|
||||
SizedBox(height: 6.0 * scaleFactor),
|
||||
AppCustomCheckbox(
|
||||
label: 'Naissance multiple',
|
||||
value: widget.childData.multipleBirth,
|
||||
onChanged: widget.onToggleMultipleBirth,
|
||||
checkboxSize: 22.0 * 1.1, // 24.2
|
||||
checkboxSize: config.isMobile ? 20.0 : 22.0 * scaleFactor,
|
||||
fontSize: config.isMobile ? 13.0 : 16.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -189,8 +210,8 @@ class _ChildCardWidgetState extends State<ChildCardWidget> {
|
||||
customBorder: const CircleBorder(),
|
||||
child: Image.asset(
|
||||
'assets/images/red_cross2.png',
|
||||
width: 36,
|
||||
height: 36,
|
||||
width: config.isMobile ? 30 : 36,
|
||||
height: config.isMobile ? 30 : 36,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user