- Réduction de la taille des polices et des champs dans la carte enfant (Mobile/Desktop) pour éviter l'overflow. - Restauration de la taille du bouton "+" en mode Desktop (100px). Co-authored-by: Cursor <cursoragent@cursor.com>
272 lines
11 KiB
Dart
272 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'dart:io' show File;
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
import '../models/user_registration_data.dart';
|
|
import '../models/card_assets.dart';
|
|
import 'custom_app_text_field.dart';
|
|
import 'form_field_wrapper.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
|
|
class ChildCardWidget extends StatefulWidget {
|
|
final ChildData childData;
|
|
final int childIndex;
|
|
final VoidCallback onPickImage;
|
|
final VoidCallback onDateSelect;
|
|
final ValueChanged<String> onFirstNameChanged;
|
|
final ValueChanged<String> onLastNameChanged;
|
|
final ValueChanged<bool> onTogglePhotoConsent;
|
|
final ValueChanged<bool> onToggleMultipleBirth;
|
|
final ValueChanged<bool> onToggleIsUnborn;
|
|
final VoidCallback onRemove;
|
|
final bool canBeRemoved;
|
|
final DisplayMode mode;
|
|
final VoidCallback? onEdit;
|
|
|
|
const ChildCardWidget({
|
|
required Key key,
|
|
required this.childData,
|
|
required this.childIndex,
|
|
required this.onPickImage,
|
|
required this.onDateSelect,
|
|
required this.onFirstNameChanged,
|
|
required this.onLastNameChanged,
|
|
required this.onTogglePhotoConsent,
|
|
required this.onToggleMultipleBirth,
|
|
required this.onToggleIsUnborn,
|
|
required this.onRemove,
|
|
required this.canBeRemoved,
|
|
this.mode = DisplayMode.editable,
|
|
this.onEdit,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<ChildCardWidget> createState() => _ChildCardWidgetState();
|
|
}
|
|
|
|
class _ChildCardWidgetState extends State<ChildCardWidget> {
|
|
late TextEditingController _firstNameController;
|
|
late TextEditingController _lastNameController;
|
|
late TextEditingController _dobController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Initialiser les contrôleurs avec les données du widget
|
|
_firstNameController = TextEditingController(text: widget.childData.firstName);
|
|
_lastNameController = TextEditingController(text: widget.childData.lastName);
|
|
_dobController = TextEditingController(text: widget.childData.dob);
|
|
|
|
// Ajouter des listeners pour mettre à jour les données sources via les callbacks
|
|
_firstNameController.addListener(() => widget.onFirstNameChanged(_firstNameController.text));
|
|
_lastNameController.addListener(() => widget.onLastNameChanged(_lastNameController.text));
|
|
// Pour dob, la mise à jour se fait via _selectDate, pas besoin de listener ici
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant ChildCardWidget oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
// Mettre à jour les contrôleurs si les données externes changent
|
|
// (peut arriver si on recharge l'état global)
|
|
if (widget.childData.firstName != _firstNameController.text) {
|
|
_firstNameController.text = widget.childData.firstName;
|
|
}
|
|
if (widget.childData.lastName != _lastNameController.text) {
|
|
_lastNameController.text = widget.childData.lastName;
|
|
}
|
|
if (widget.childData.dob != _dobController.text) {
|
|
_dobController.text = widget.childData.dob;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_firstNameController.dispose();
|
|
_lastNameController.dispose();
|
|
_dobController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final config = DisplayConfig.fromContext(context, mode: widget.mode);
|
|
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
|
|
? Colors.purple.shade200
|
|
: (widget.childData.cardColor == CardColorVertical.pink ? Colors.pink.shade200 : Colors.grey.shade200); // Placeholder pour autres couleurs
|
|
final Color initialPhotoShadow = baseCardColorForShadow.withAlpha(90);
|
|
final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130);
|
|
|
|
return Container(
|
|
width: 345.0 * scaleFactor,
|
|
height: config.isMobile ? null : 600.0 * scaleFactor, // Hauteur augmentée pour éviter l'overflow
|
|
padding: EdgeInsets.all(22.0 * scaleFactor),
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(image: AssetImage(widget.childData.cardColor.path), fit: BoxFit.fill),
|
|
borderRadius: BorderRadius.circular(20 * scaleFactor),
|
|
),
|
|
child: Stack(
|
|
children: [
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
HoverReliefWidget(
|
|
onPressed: config.isReadonly ? null : widget.onPickImage,
|
|
borderRadius: BorderRadius.circular(10),
|
|
initialShadowColor: initialPhotoShadow,
|
|
hoverShadowColor: hoverPhotoShadow,
|
|
child: SizedBox(
|
|
height: 200.0 * (config.isMobile ? 0.8 : 1.0),
|
|
width: 200.0 * (config.isMobile ? 0.8 : 1.0),
|
|
child: Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(5.0 * scaleFactor),
|
|
child: currentChildImage != null
|
|
? 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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 10.0 * scaleFactor), // Réduit de 12 à 10
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
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: config.isReadonly ? null : widget.onToggleIsUnborn,
|
|
activeColor: Theme.of(context).primaryColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8.0 * scaleFactor), // Réduit de 9 à 8
|
|
_buildField(
|
|
config: config,
|
|
scaleFactor: scaleFactor,
|
|
label: 'Prénom',
|
|
controller: _firstNameController,
|
|
hint: 'Facultatif si à naître',
|
|
isRequired: !widget.childData.isUnbornChild,
|
|
),
|
|
SizedBox(height: 5.0 * scaleFactor), // Réduit de 6 à 5
|
|
_buildField(
|
|
config: config,
|
|
scaleFactor: scaleFactor,
|
|
label: 'Nom',
|
|
controller: _lastNameController,
|
|
hint: 'Nom de l\'enfant',
|
|
),
|
|
SizedBox(height: 8.0 * scaleFactor), // Réduit de 9 à 8
|
|
_buildField(
|
|
config: config,
|
|
scaleFactor: scaleFactor,
|
|
label: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance',
|
|
controller: _dobController,
|
|
hint: 'JJ/MM/AAAA',
|
|
readOnly: true, // Toujours readonly pour le TextField (date picker)
|
|
onTap: config.isReadonly ? null : widget.onDateSelect,
|
|
suffixIcon: Icons.calendar_today,
|
|
),
|
|
SizedBox(height: 10.0 * scaleFactor), // Réduit de 11 à 10
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
AppCustomCheckbox(
|
|
label: 'Consentement photo',
|
|
value: widget.childData.photoConsent,
|
|
onChanged: config.isReadonly ? (v) {} : widget.onTogglePhotoConsent,
|
|
checkboxSize: config.isMobile ? 20.0 : 22.0 * scaleFactor,
|
|
fontSize: config.isMobile ? 13.0 : 16.0,
|
|
),
|
|
SizedBox(height: 5.0 * scaleFactor), // Réduit de 6 à 5
|
|
AppCustomCheckbox(
|
|
label: 'Naissance multiple',
|
|
value: widget.childData.multipleBirth,
|
|
onChanged: config.isReadonly ? (v) {} : widget.onToggleMultipleBirth,
|
|
checkboxSize: config.isMobile ? 20.0 : 22.0 * scaleFactor,
|
|
fontSize: config.isMobile ? 13.0 : 16.0,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
if (widget.canBeRemoved && !config.isReadonly)
|
|
Positioned(
|
|
top: -5, right: -5,
|
|
child: InkWell(
|
|
onTap: widget.onRemove,
|
|
customBorder: const CircleBorder(),
|
|
child: Image.asset(
|
|
'assets/images/red_cross2.png',
|
|
width: config.isMobile ? 30 : 36,
|
|
height: config.isMobile ? 30 : 36,
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
|
|
if (config.isReadonly && widget.onEdit != null)
|
|
Positioned(
|
|
top: -5, right: -5,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.edit, color: Colors.black54),
|
|
onPressed: widget.onEdit,
|
|
tooltip: 'Modifier',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildField({
|
|
required DisplayConfig config,
|
|
required double scaleFactor,
|
|
required String label,
|
|
required TextEditingController controller,
|
|
String? hint,
|
|
bool isRequired = false,
|
|
bool readOnly = false,
|
|
VoidCallback? onTap,
|
|
IconData? suffixIcon,
|
|
}) {
|
|
if (config.isReadonly) {
|
|
return FormFieldWrapper(
|
|
config: config,
|
|
label: label,
|
|
value: controller.text,
|
|
);
|
|
} else {
|
|
return CustomAppTextField(
|
|
controller: controller,
|
|
labelText: label,
|
|
hintText: hint ?? label,
|
|
isRequired: isRequired,
|
|
fieldHeight: config.isMobile ? 40.0 : 50.0 * scaleFactor, // Hauteur réduite
|
|
labelFontSize: config.isMobile ? 12.0 : 18.0, // Police réduite
|
|
inputFontSize: config.isMobile ? 13.0 : 16.0, // Police réduite
|
|
readOnly: readOnly,
|
|
onTap: onTap,
|
|
suffixIcon: suffixIcon,
|
|
);
|
|
}
|
|
}
|
|
}
|