- Étape 1 : prénom/nom min 2 caractères (minPersonNameLength sur PersonalInfoFormScreen pour AM) - Étape 2 : ville/pays naissance min 2 ; photo réelle + consentement obligatoires ; isStep2Complete strict - registerAM : lieu naissance trim + toujours envoyer photo_base64 ; lieu_* non null si valide - Étape 4 : blocage envoi si isRegistrationComplete faux Made-with: Cursor
1308 lines
45 KiB
Dart
1308 lines
45 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import 'package:p_tits_pas/utils/phone_utils.dart';
|
||
import 'package:p_tits_pas/utils/name_format_utils.dart';
|
||
import 'package:p_tits_pas/utils/email_utils.dart';
|
||
import 'package:p_tits_pas/utils/postal_utils.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'dart:math' as math;
|
||
|
||
import 'custom_app_text_field.dart';
|
||
import 'form_field_wrapper.dart';
|
||
import 'hover_relief_widget.dart';
|
||
import 'custom_navigation_button.dart';
|
||
import '../models/card_assets.dart';
|
||
import '../config/display_config.dart';
|
||
|
||
/// Modèle de données pour le formulaire
|
||
class PersonalInfoData {
|
||
String firstName;
|
||
String lastName;
|
||
String phone;
|
||
String email;
|
||
String address;
|
||
String postalCode;
|
||
String city;
|
||
|
||
PersonalInfoData({
|
||
this.firstName = '',
|
||
this.lastName = '',
|
||
this.phone = '',
|
||
this.email = '',
|
||
this.address = '',
|
||
this.postalCode = '',
|
||
this.city = '',
|
||
});
|
||
}
|
||
|
||
/// Widget générique pour les formulaires d'informations personnelles
|
||
/// Supporte mode éditable et readonly, responsive mobile/desktop
|
||
class PersonalInfoFormScreen extends StatefulWidget {
|
||
final DisplayMode mode; // editable ou readonly
|
||
final String stepText; // Ex: "Étape 1/5"
|
||
final String title; // Ex: "Informations du Parent Principal"
|
||
final CardColorHorizontal cardColor;
|
||
final PersonalInfoData initialData;
|
||
final Function(PersonalInfoData data, {bool? hasSecondPerson, bool? sameAddress}) onSubmit;
|
||
final String previousRoute;
|
||
|
||
// Options spécifiques pour Parent 2
|
||
final bool showSecondPersonToggle; // Afficher "Il y a un 2ème parent"
|
||
final bool? initialHasSecondPerson;
|
||
final bool showSameAddressCheckbox; // Afficher "Même adresse que parent 1"
|
||
final bool? initialSameAddress;
|
||
final PersonalInfoData? referenceAddressData; // Pour pré-remplir si "même adresse"
|
||
final bool embedContentOnly; // Si true, affiche seulement la carte (sans scaffold/fond/titre)
|
||
final VoidCallback? onEdit; // Callback pour le bouton d'édition (si affiché)
|
||
/// Si non null (ex. 2 pour inscription AM), nom/prénom : trim + longueur minimale (aligné API).
|
||
final int? minPersonNameLength;
|
||
|
||
const PersonalInfoFormScreen({
|
||
super.key,
|
||
this.mode = DisplayMode.editable,
|
||
required this.stepText,
|
||
required this.title,
|
||
required this.cardColor,
|
||
required this.initialData,
|
||
required this.onSubmit,
|
||
required this.previousRoute,
|
||
this.showSecondPersonToggle = false,
|
||
this.initialHasSecondPerson,
|
||
this.showSameAddressCheckbox = false,
|
||
this.initialSameAddress,
|
||
this.referenceAddressData,
|
||
this.embedContentOnly = false,
|
||
this.onEdit,
|
||
this.minPersonNameLength,
|
||
});
|
||
|
||
@override
|
||
State<PersonalInfoFormScreen> createState() => _PersonalInfoFormScreenState();
|
||
}
|
||
|
||
class _PersonalInfoFormScreenState extends State<PersonalInfoFormScreen> {
|
||
/// Ordre de tabulation explicite (étape 1 / parent 2, etc.)
|
||
static const double _focusSecondPersonToggle = 1;
|
||
static const double _focusSameAddressToggle = 2;
|
||
static const double _focusLastName = 10;
|
||
static const double _focusFirstName = 11;
|
||
static const double _focusPhone = 12;
|
||
static const double _focusEmail = 13;
|
||
static const double _focusAddress = 14;
|
||
static const double _focusPostal = 15;
|
||
static const double _focusCity = 16;
|
||
static const double _focusNavPrevious = 100;
|
||
static const double _focusNavNext = 101;
|
||
|
||
final _formKey = GlobalKey<FormState>();
|
||
late TextEditingController _lastNameController;
|
||
late TextEditingController _firstNameController;
|
||
late TextEditingController _phoneController;
|
||
late TextEditingController _emailController;
|
||
late TextEditingController _addressController;
|
||
late TextEditingController _postalCodeController;
|
||
late TextEditingController _cityController;
|
||
|
||
bool _hasSecondPerson = false;
|
||
bool _sameAddress = false;
|
||
bool _fieldsEnabled = true;
|
||
|
||
FocusNode? _lastNameFocus;
|
||
FocusNode? _firstNameFocus;
|
||
FocusNode? _cityFocus;
|
||
FocusNode? _emailFocus;
|
||
final GlobalKey<FormFieldState<String>> _emailFormKey =
|
||
GlobalKey<FormFieldState<String>>();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_lastNameController = TextEditingController(text: widget.initialData.lastName);
|
||
_firstNameController = TextEditingController(text: widget.initialData.firstName);
|
||
_phoneController = TextEditingController(text: formatPhoneForDisplay(widget.initialData.phone));
|
||
_emailController = TextEditingController(
|
||
text: normalizeEmailText(widget.initialData.email),
|
||
);
|
||
_addressController = TextEditingController(text: widget.initialData.address);
|
||
_postalCodeController = TextEditingController(text: widget.initialData.postalCode);
|
||
_cityController = TextEditingController(text: widget.initialData.city);
|
||
|
||
if (widget.showSecondPersonToggle) {
|
||
_hasSecondPerson = widget.initialHasSecondPerson ?? true;
|
||
_fieldsEnabled = _hasSecondPerson;
|
||
}
|
||
|
||
if (widget.showSameAddressCheckbox) {
|
||
_sameAddress = widget.initialSameAddress ?? false;
|
||
_updateAddressFields();
|
||
}
|
||
|
||
if (widget.mode == DisplayMode.editable) {
|
||
_lastNameFocus = FocusNode();
|
||
_firstNameFocus = FocusNode();
|
||
_cityFocus = FocusNode();
|
||
_emailFocus = FocusNode();
|
||
_lastNameFocus!.addListener(_onLastNameFocusChange);
|
||
_firstNameFocus!.addListener(_onFirstNameFocusChange);
|
||
_cityFocus!.addListener(_onCityFocusChange);
|
||
_emailFocus!.addListener(_onEmailFocusChange);
|
||
}
|
||
}
|
||
|
||
void _onLastNameFocusChange() {
|
||
if (_lastNameFocus == null || _lastNameFocus!.hasFocus) {
|
||
return;
|
||
}
|
||
_applyPersonNameFormat(_lastNameController);
|
||
}
|
||
|
||
void _onFirstNameFocusChange() {
|
||
if (_firstNameFocus == null || _firstNameFocus!.hasFocus) {
|
||
return;
|
||
}
|
||
_applyPersonNameFormat(_firstNameController);
|
||
}
|
||
|
||
void _onCityFocusChange() {
|
||
if (_cityFocus == null || _cityFocus!.hasFocus) {
|
||
return;
|
||
}
|
||
_applyPersonNameFormat(_cityController);
|
||
}
|
||
|
||
void _onEmailFocusChange() {
|
||
if (_emailFocus == null || _emailFocus!.hasFocus) {
|
||
return;
|
||
}
|
||
if (widget.showSecondPersonToggle && !_fieldsEnabled) {
|
||
return;
|
||
}
|
||
// Reporter normalisation + validate au frame suivant pour ne pas casser Tab.
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
if (!mounted || _emailFocus == null || _emailFocus!.hasFocus) {
|
||
return;
|
||
}
|
||
if (widget.showSecondPersonToggle && !_fieldsEnabled) {
|
||
return;
|
||
}
|
||
final normalized = normalizeEmailText(_emailController.text);
|
||
if (normalized != _emailController.text) {
|
||
_emailController.value = TextEditingValue(
|
||
text: normalized,
|
||
selection: TextSelection.collapsed(offset: normalized.length),
|
||
);
|
||
}
|
||
// Pas d’erreur « obligatoire » au blur si le champ est encore vide :
|
||
// évite un message dès l’activation du parent 2 (focus / rebuild) et
|
||
// la soumission du formulaire valide toujours.
|
||
if (normalizeEmailText(_emailController.text).isEmpty) {
|
||
return;
|
||
}
|
||
_emailFormKey.currentState?.validate();
|
||
});
|
||
}
|
||
|
||
String? _validateRequiredFrenchPhone(String? value) {
|
||
if (value == null || value.trim().isEmpty) {
|
||
return 'Ce champ est obligatoire';
|
||
}
|
||
return validateFrenchNationalPhone(value, allowEmpty: false);
|
||
}
|
||
|
||
String? _validateRequiredEmail(String? value) {
|
||
if (value == null || value.trim().isEmpty) {
|
||
return 'Ce champ est obligatoire.';
|
||
}
|
||
return validateEmail(value, allowEmpty: true);
|
||
}
|
||
|
||
/// Prénom / nom avec longueur minimale (ex. inscription AM, aligné `MinLength` API).
|
||
String? _validatePersonNameWithMinLength(String? value) {
|
||
final min = widget.minPersonNameLength;
|
||
if (min == null) return null;
|
||
final t = (value ?? '').trim();
|
||
if (t.isEmpty) return 'Ce champ est obligatoire';
|
||
if (t.length < min) return 'Au moins $min caractères';
|
||
return null;
|
||
}
|
||
|
||
/// Téléphone / e-mail du parent 2 : pas de validation si « Ajouter Parent 2 » est désactivé.
|
||
String? _validateFrenchPhoneIfSecondParentNeeded(String? value) {
|
||
if (widget.showSecondPersonToggle && !_fieldsEnabled) {
|
||
return null;
|
||
}
|
||
return _validateRequiredFrenchPhone(value);
|
||
}
|
||
|
||
String? _validateEmailIfSecondParentNeeded(String? value) {
|
||
if (widget.showSecondPersonToggle && !_fieldsEnabled) {
|
||
return null;
|
||
}
|
||
return _validateRequiredEmail(value);
|
||
}
|
||
|
||
/// Adresse / code postal / ville : pas de contrôle si « Même adresse » (valeurs prises du parent 1).
|
||
String? _validateAddressLineIfManualEntry(String? value) {
|
||
if (!_fieldsEnabled) {
|
||
return null;
|
||
}
|
||
if (widget.showSameAddressCheckbox && _sameAddress) {
|
||
return null;
|
||
}
|
||
if (value == null || value.trim().isEmpty) {
|
||
return 'Ce champ est obligatoire';
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// Code postal : 5 chiffres ; ignoré si parent 2 désactivé ou « Même adresse ».
|
||
String? _validatePostalCodeField(String? value) {
|
||
if (!_fieldsEnabled) {
|
||
return null;
|
||
}
|
||
if (widget.showSameAddressCheckbox && _sameAddress) {
|
||
return null;
|
||
}
|
||
return validateFrenchPostalCode(value, allowEmpty: false);
|
||
}
|
||
|
||
void _applyPersonNameFormat(TextEditingController controller) {
|
||
final formatted = formatPersonNameCase(controller.text);
|
||
if (formatted == controller.text) {
|
||
return;
|
||
}
|
||
controller.value = TextEditingValue(
|
||
text: formatted,
|
||
selection: TextSelection.collapsed(offset: formatted.length),
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_lastNameFocus?.removeListener(_onLastNameFocusChange);
|
||
_firstNameFocus?.removeListener(_onFirstNameFocusChange);
|
||
_cityFocus?.removeListener(_onCityFocusChange);
|
||
_emailFocus?.removeListener(_onEmailFocusChange);
|
||
_lastNameFocus?.dispose();
|
||
_firstNameFocus?.dispose();
|
||
_cityFocus?.dispose();
|
||
_emailFocus?.dispose();
|
||
_lastNameController.dispose();
|
||
_firstNameController.dispose();
|
||
_phoneController.dispose();
|
||
_emailController.dispose();
|
||
_addressController.dispose();
|
||
_postalCodeController.dispose();
|
||
_cityController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _updateAddressFields() {
|
||
if (_sameAddress && widget.referenceAddressData != null) {
|
||
_addressController.text = widget.referenceAddressData!.address;
|
||
_postalCodeController.text = widget.referenceAddressData!.postalCode;
|
||
_cityController.text = widget.referenceAddressData!.city;
|
||
}
|
||
}
|
||
|
||
Widget _wrapScrollBodyIfEditable({
|
||
required DisplayConfig config,
|
||
required Widget child,
|
||
}) {
|
||
if (!config.isEditable) return child;
|
||
return FocusTraversalGroup(
|
||
policy: OrderedTraversalPolicy(),
|
||
child: child,
|
||
);
|
||
}
|
||
|
||
Widget _orderedFocus({
|
||
required bool enabled,
|
||
required double order,
|
||
required Widget child,
|
||
}) {
|
||
if (!enabled) return child;
|
||
return FocusTraversalOrder(
|
||
order: NumericFocusOrder(order),
|
||
child: child,
|
||
);
|
||
}
|
||
|
||
void _handleSubmit() {
|
||
if (widget.mode == DisplayMode.editable) {
|
||
_applyPersonNameFormat(_lastNameController);
|
||
_applyPersonNameFormat(_firstNameController);
|
||
_applyPersonNameFormat(_cityController);
|
||
}
|
||
// Parent 2 désactivé : pas de contrôle sur les champs (désactivés à l’écran).
|
||
if (widget.showSecondPersonToggle && !_hasSecondPerson) {
|
||
widget.onSubmit(
|
||
PersonalInfoData(),
|
||
hasSecondPerson: false,
|
||
sameAddress: widget.showSameAddressCheckbox ? _sameAddress : null,
|
||
);
|
||
return;
|
||
}
|
||
if (widget.mode == DisplayMode.readonly || _formKey.currentState!.validate()) {
|
||
final data = PersonalInfoData(
|
||
firstName: _firstNameController.text,
|
||
lastName: _lastNameController.text,
|
||
phone: normalizePhone(_phoneController.text),
|
||
email: normalizeEmailText(_emailController.text),
|
||
address: _addressController.text,
|
||
postalCode: _postalCodeController.text,
|
||
city: _cityController.text,
|
||
);
|
||
|
||
widget.onSubmit(
|
||
data,
|
||
hasSecondPerson: widget.showSecondPersonToggle ? _hasSecondPerson : null,
|
||
sameAddress: widget.showSameAddressCheckbox ? _sameAddress : null,
|
||
);
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final screenSize = MediaQuery.of(context).size;
|
||
final config = DisplayConfig.fromContext(context, mode: widget.mode);
|
||
|
||
if (widget.embedContentOnly) {
|
||
return _buildCard(context, config, screenSize);
|
||
}
|
||
|
||
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: _wrapScrollBodyIfEditable(
|
||
config: config,
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(
|
||
widget.stepText,
|
||
style: GoogleFonts.merienda(
|
||
fontSize: config.isMobile ? 13 : 16,
|
||
color: Colors.black54,
|
||
),
|
||
),
|
||
SizedBox(height: config.isMobile ? 6 : 10),
|
||
Text(
|
||
widget.title,
|
||
style: GoogleFonts.merienda(
|
||
fontSize: config.isMobile ? 18 : 24,
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.black87,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
SizedBox(height: config.isMobile ? 16 : 30),
|
||
_buildCard(context, config, screenSize),
|
||
if (config.isMobile) ...[
|
||
const SizedBox(height: 20),
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: screenSize.width * 0.05,
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: _orderedFocus(
|
||
enabled: config.isEditable,
|
||
order: _focusNavPrevious,
|
||
child: HoverReliefWidget(
|
||
child: CustomNavigationButton(
|
||
text: 'Précédent',
|
||
style: NavigationButtonStyle.purple,
|
||
onPressed: () {
|
||
if (context.canPop()) {
|
||
context.pop();
|
||
} else {
|
||
context.go(widget.previousRoute);
|
||
}
|
||
},
|
||
width: double.infinity,
|
||
height: 50,
|
||
fontSize: 16,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 16),
|
||
Expanded(
|
||
child: _orderedFocus(
|
||
enabled: config.isEditable,
|
||
order: _focusNavNext,
|
||
child: HoverReliefWidget(
|
||
child: CustomNavigationButton(
|
||
text: 'Suivant',
|
||
style: NavigationButtonStyle.green,
|
||
onPressed: _handleSubmit,
|
||
width: double.infinity,
|
||
height: 50,
|
||
fontSize: 16,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 10),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
// Chevrons de navigation (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(widget.previousRoute);
|
||
}
|
||
},
|
||
tooltip: 'Retour',
|
||
),
|
||
),
|
||
Positioned(
|
||
top: screenSize.height / 2 - 20,
|
||
right: 40,
|
||
child: IconButton(
|
||
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||
onPressed: _handleSubmit,
|
||
tooltip: 'Suivant',
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildCard(BuildContext context, DisplayConfig config, Size screenSize) {
|
||
// En mode readonly desktop avec embedContentOnly, utiliser le layout ancien (AspectRatio 2:1)
|
||
if (config.isReadonly && !config.isMobile && widget.embedContentOnly) {
|
||
return _buildReadonlyDesktopCard(context, config, screenSize);
|
||
}
|
||
|
||
// En mode readonly mobile avec embedContentOnly, forcer le ratio 1:2 (Vertical)
|
||
if (config.isReadonly && config.isMobile && widget.embedContentOnly) {
|
||
return Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.05), // Marge 5%
|
||
child: _buildMobileReadonlyCard(context, config, screenSize),
|
||
);
|
||
}
|
||
|
||
// Mode normal (éditable ou mobile non-récap)
|
||
return Stack(
|
||
clipBehavior: Clip.none,
|
||
children: [
|
||
Container(
|
||
width: config.isMobile ? screenSize.width * 0.9 : screenSize.width * 0.6,
|
||
padding: EdgeInsets.symmetric(
|
||
vertical: config.isMobile ? 20 : (config.isReadonly ? 30 : 50),
|
||
horizontal: config.isMobile ? 24 : 50,
|
||
),
|
||
decoration: BoxDecoration(
|
||
image: DecorationImage(
|
||
image: AssetImage(
|
||
config.isMobile
|
||
? _getVerticalCardAsset()
|
||
: widget.cardColor.path
|
||
),
|
||
fit: BoxFit.fill,
|
||
),
|
||
),
|
||
child: Form(
|
||
key: _formKey,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
if (widget.embedContentOnly) ...[
|
||
Text(
|
||
// Titre raccourci sur mobile en mode readonly pour laisser place au bouton edit
|
||
(config.isMobile && config.isReadonly && widget.title.contains('Parent Principal'))
|
||
? 'Parent Principal'
|
||
: widget.title,
|
||
style: GoogleFonts.merienda(
|
||
fontSize: config.isMobile ? 18 : 22,
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.black87,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
const SizedBox(height: 20),
|
||
],
|
||
|
||
if (config.isEditable && widget.showSecondPersonToggle)
|
||
_buildToggles(context, config),
|
||
|
||
_buildFormFields(context, config),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
if (config.isReadonly && widget.onEdit != null)
|
||
Positioned(
|
||
top: 10,
|
||
right: 10,
|
||
child: IconButton(
|
||
icon: const Icon(Icons.edit, color: Colors.black54, size: 28),
|
||
onPressed: widget.onEdit,
|
||
tooltip: 'Modifier',
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// Carte en mode readonly MOBILE avec hauteur adaptative
|
||
Widget _buildMobileReadonlyCard(BuildContext context, DisplayConfig config, Size screenSize) {
|
||
return Container(
|
||
width: double.infinity,
|
||
// Pas de height fixe, s'adapte au contenu
|
||
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 24.0),
|
||
decoration: BoxDecoration(
|
||
image: DecorationImage(
|
||
image: AssetImage(_getVerticalCardAsset()),
|
||
fit: BoxFit.fill, // Fill pour que l'image s'étire selon la hauteur du contenu
|
||
),
|
||
borderRadius: BorderRadius.circular(15),
|
||
),
|
||
child: Stack(
|
||
children: [
|
||
Column(
|
||
mainAxisSize: MainAxisSize.min, // Important : prend le min de place
|
||
children: [
|
||
// Titre + Edit Button
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Text(
|
||
(widget.title.contains('Parent Principal') ? 'Parent Principal' : (widget.title.contains('Deuxième Parent') ? 'Deuxième Parent' : widget.title)),
|
||
style: GoogleFonts.merienda(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
),
|
||
if (widget.onEdit != null)
|
||
const SizedBox(width: 28),
|
||
],
|
||
),
|
||
|
||
// Contenu aligné en haut (plus d'Expanded ici car parent non contraint)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 20.0),
|
||
child: _buildFormFields(context, config),
|
||
),
|
||
],
|
||
),
|
||
|
||
if (widget.onEdit != null)
|
||
Positioned(
|
||
top: 0,
|
||
right: 0,
|
||
child: IconButton(
|
||
padding: EdgeInsets.zero,
|
||
constraints: const BoxConstraints(),
|
||
icon: const Icon(Icons.edit, color: Colors.black54, size: 24),
|
||
onPressed: widget.onEdit,
|
||
tooltip: 'Modifier',
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// Carte en mode readonly desktop avec AspectRatio 2:1 (format de l'ancien récapitulatif)
|
||
Widget _buildReadonlyDesktopCard(BuildContext context, DisplayConfig config, Size screenSize) {
|
||
// Largeur de la carte : 50% de l'écran (comme l'ancien système)
|
||
final cardWidth = screenSize.width / 2.0;
|
||
|
||
return SizedBox(
|
||
width: cardWidth,
|
||
child: AspectRatio(
|
||
aspectRatio: 2.0,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 25.0),
|
||
decoration: BoxDecoration(
|
||
image: DecorationImage(
|
||
image: AssetImage(widget.cardColor.path),
|
||
fit: BoxFit.cover,
|
||
),
|
||
borderRadius: BorderRadius.circular(15),
|
||
),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Titre centré
|
||
Align(
|
||
alignment: Alignment.center,
|
||
child: Text(
|
||
widget.title,
|
||
style: GoogleFonts.merienda(
|
||
fontSize: 22,
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.black87,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 55), // Espace très augmenté pour pousser les champs vers le bas
|
||
// Champs du formulaire
|
||
_buildFormFields(context, config),
|
||
],
|
||
),
|
||
),
|
||
// Bouton d'édition à droite
|
||
if (widget.onEdit != null)
|
||
IconButton(
|
||
icon: const Icon(Icons.edit, color: Colors.black54, size: 28),
|
||
onPressed: widget.onEdit,
|
||
tooltip: 'Modifier',
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// Construit les toggles (Parent 2 / Même adresse)
|
||
Widget _buildToggles(BuildContext context, DisplayConfig config) {
|
||
if (config.isMobile) {
|
||
// Layout vertical sur mobile - toggles compacts
|
||
return Column(
|
||
children: [
|
||
_buildSecondPersonToggle(context, config),
|
||
if (widget.showSameAddressCheckbox) ...[
|
||
const SizedBox(height: 5), // Réduit de 12 à 5
|
||
_buildSameAddressToggle(context, config),
|
||
],
|
||
const SizedBox(height: 10), // Réduit de 24 à 10
|
||
],
|
||
);
|
||
} else {
|
||
// Layout horizontal sur desktop
|
||
return Column(
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
flex: 12,
|
||
child: _buildSecondPersonToggle(context, config),
|
||
),
|
||
const Expanded(flex: 1, child: SizedBox()),
|
||
if (widget.showSameAddressCheckbox)
|
||
Expanded(
|
||
flex: 12,
|
||
child: _buildSameAddressToggle(context, config),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 32),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
Widget _buildSecondPersonToggle(BuildContext context, DisplayConfig config) {
|
||
return Row(
|
||
children: [
|
||
Icon(Icons.person_add_alt_1, size: config.isMobile ? 18 : 20),
|
||
SizedBox(width: config.isMobile ? 6 : 8),
|
||
Expanded(
|
||
child: Text(
|
||
'Ajouter Parent 2 ?',
|
||
style: GoogleFonts.merienda(
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: config.isMobile ? 14 : 16,
|
||
),
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
_orderedFocus(
|
||
enabled: config.isEditable,
|
||
order: _focusSecondPersonToggle,
|
||
child: Transform.scale(
|
||
scale: config.isMobile ? 0.85 : 1.0,
|
||
child: Switch(
|
||
value: _hasSecondPerson,
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_hasSecondPerson = value;
|
||
_fieldsEnabled = value;
|
||
if (value && _sameAddress) {
|
||
_updateAddressFields();
|
||
}
|
||
});
|
||
if (!value) {
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
// Si l’utilisateur a déjà recoché Parent 2, ne pas valider :
|
||
// sinon des champs vides affichent une erreur tout de suite.
|
||
if (!mounted || _hasSecondPerson) {
|
||
return;
|
||
}
|
||
_formKey.currentState?.validate();
|
||
});
|
||
}
|
||
},
|
||
activeColor: Theme.of(context).primaryColor,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildSameAddressToggle(BuildContext context, DisplayConfig config) {
|
||
return Row(
|
||
children: [
|
||
Icon(
|
||
Icons.home_work_outlined,
|
||
size: config.isMobile ? 18 : 20,
|
||
color: _fieldsEnabled ? null : Colors.grey,
|
||
),
|
||
SizedBox(width: config.isMobile ? 6 : 8),
|
||
Expanded(
|
||
child: Text(
|
||
'Même Adresse ?',
|
||
style: GoogleFonts.merienda(
|
||
color: _fieldsEnabled ? null : Colors.grey,
|
||
fontSize: config.isMobile ? 14 : 16,
|
||
),
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
_orderedFocus(
|
||
enabled: config.isEditable,
|
||
order: _focusSameAddressToggle,
|
||
child: Transform.scale(
|
||
scale: config.isMobile ? 0.85 : 1.0,
|
||
child: Switch(
|
||
value: _sameAddress,
|
||
onChanged: _fieldsEnabled ? (value) {
|
||
setState(() {
|
||
_sameAddress = value;
|
||
_updateAddressFields();
|
||
});
|
||
} : null,
|
||
activeColor: Theme.of(context).primaryColor,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// Construit les champs du formulaire avec la nouvelle infrastructure
|
||
Widget _buildFormFields(BuildContext context, DisplayConfig config) {
|
||
if (config.isMobile) {
|
||
return _buildMobileFields(context, config);
|
||
} else {
|
||
return _buildDesktopFields(context, config);
|
||
}
|
||
}
|
||
|
||
/// Layout DESKTOP : champs côte à côte (horizontal)
|
||
Widget _buildDesktopFields(BuildContext context, DisplayConfig config) {
|
||
// En mode readonly, utiliser l'ancien layout du récapitulatif
|
||
if (config.isReadonly) {
|
||
return _buildReadonlyDesktopFields(context);
|
||
}
|
||
|
||
// Mode éditable : layout normal
|
||
final double verticalSpacing = 32.0;
|
||
|
||
return Column(
|
||
children: [
|
||
// Nom et Prénom
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildField(
|
||
config: config,
|
||
label: 'Nom',
|
||
controller: _lastNameController,
|
||
hint: 'Votre nom de famille',
|
||
enabled: _fieldsEnabled,
|
||
focusOrder: _focusLastName,
|
||
focusNode: _lastNameFocus,
|
||
fieldValidator: widget.minPersonNameLength != null
|
||
? _validatePersonNameWithMinLength
|
||
: null,
|
||
),
|
||
),
|
||
const SizedBox(width: 20),
|
||
Expanded(
|
||
child: _buildField(
|
||
config: config,
|
||
label: 'Prénom',
|
||
controller: _firstNameController,
|
||
hint: 'Votre prénom',
|
||
enabled: _fieldsEnabled,
|
||
focusOrder: _focusFirstName,
|
||
focusNode: _firstNameFocus,
|
||
fieldValidator: widget.minPersonNameLength != null
|
||
? _validatePersonNameWithMinLength
|
||
: null,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(height: verticalSpacing),
|
||
|
||
// Téléphone et Email
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildField(
|
||
config: config,
|
||
label: 'Téléphone',
|
||
controller: _phoneController,
|
||
hint: 'Votre numéro de téléphone',
|
||
keyboardType: TextInputType.phone,
|
||
enabled: _fieldsEnabled,
|
||
inputFormatters: frenchPhoneInputFormatters,
|
||
focusOrder: _focusPhone,
|
||
fieldValidator: _validateFrenchPhoneIfSecondParentNeeded,
|
||
),
|
||
),
|
||
const SizedBox(width: 20),
|
||
Expanded(
|
||
child: _buildField(
|
||
config: config,
|
||
label: 'Email',
|
||
controller: _emailController,
|
||
hint: 'Votre adresse e-mail',
|
||
keyboardType: TextInputType.emailAddress,
|
||
enabled: _fieldsEnabled,
|
||
focusOrder: _focusEmail,
|
||
fieldValidator: _validateEmailIfSecondParentNeeded,
|
||
inputFormatters: const [EmailMaxLengthFormatter()],
|
||
focusNode: _emailFocus,
|
||
formFieldKey: _emailFormKey,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(height: verticalSpacing),
|
||
|
||
// Adresse
|
||
_buildField(
|
||
config: config,
|
||
label: 'Adresse (N° et Rue)',
|
||
controller: _addressController,
|
||
hint: 'Numéro et nom de votre rue',
|
||
enabled: _fieldsEnabled && !_sameAddress,
|
||
focusOrder: _focusAddress,
|
||
fieldValidator: widget.showSameAddressCheckbox
|
||
? _validateAddressLineIfManualEntry
|
||
: null,
|
||
),
|
||
SizedBox(height: verticalSpacing),
|
||
|
||
// Code Postal et Ville
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
flex: 2,
|
||
child: _buildField(
|
||
config: config,
|
||
label: 'Code Postal',
|
||
controller: _postalCodeController,
|
||
hint: '5 chiffres',
|
||
keyboardType: TextInputType.number,
|
||
enabled: _fieldsEnabled && !_sameAddress,
|
||
focusOrder: _focusPostal,
|
||
fieldValidator: _validatePostalCodeField,
|
||
inputFormatters: kFrenchPostalCodeInputFormatters,
|
||
),
|
||
),
|
||
const SizedBox(width: 20),
|
||
Expanded(
|
||
flex: 5,
|
||
child: _buildField(
|
||
config: config,
|
||
label: 'Ville',
|
||
controller: _cityController,
|
||
hint: 'Votre ville',
|
||
enabled: _fieldsEnabled && !_sameAddress,
|
||
focusOrder: _focusCity,
|
||
focusNode: _cityFocus,
|
||
fieldValidator: widget.showSameAddressCheckbox
|
||
? _validateAddressLineIfManualEntry
|
||
: null,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// Layout DESKTOP en mode READONLY : réplique l'ancien design du récapitulatif
|
||
Widget _buildReadonlyDesktopFields(BuildContext context) {
|
||
const double verticalSpacing = 20.0; // Réduit pour compacter le bas
|
||
const double labelFontSize = 22.0;
|
||
const double valueFontSize = 18.0; // Taille du texte dans les champs
|
||
|
||
return Column(
|
||
children: [
|
||
// Ligne 1 : Nom + Prénom
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Expanded(
|
||
child: _buildDisplayFieldValue(
|
||
context,
|
||
'Nom :',
|
||
_lastNameController.text,
|
||
labelFontSize: labelFontSize,
|
||
valueFontSize: valueFontSize,
|
||
),
|
||
),
|
||
const SizedBox(width: 20),
|
||
Expanded(
|
||
child: _buildDisplayFieldValue(
|
||
context,
|
||
'Prénom :',
|
||
_firstNameController.text,
|
||
labelFontSize: labelFontSize,
|
||
valueFontSize: valueFontSize,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: verticalSpacing),
|
||
|
||
// Ligne 2 : Téléphone + Email
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Expanded(
|
||
child: _buildDisplayFieldValue(
|
||
context,
|
||
'Téléphone :',
|
||
_phoneController.text.trim().isNotEmpty
|
||
? formatPhoneForDisplay(_phoneController.text)
|
||
: _phoneController.text,
|
||
labelFontSize: labelFontSize,
|
||
valueFontSize: valueFontSize,
|
||
),
|
||
),
|
||
const SizedBox(width: 20),
|
||
Expanded(
|
||
child: _buildDisplayFieldValue(
|
||
context,
|
||
'Email :',
|
||
_emailController.text,
|
||
multiLine: true,
|
||
labelFontSize: labelFontSize,
|
||
valueFontSize: valueFontSize,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: verticalSpacing),
|
||
|
||
// Ligne 3 : Adresse complète (adresse + CP + ville)
|
||
_buildDisplayFieldValue(
|
||
context,
|
||
'Adresse :',
|
||
"${_addressController.text}\n${_postalCodeController.text} ${_cityController.text}".trim(),
|
||
multiLine: true,
|
||
fieldHeight: 80,
|
||
labelFontSize: labelFontSize,
|
||
valueFontSize: valueFontSize,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// Helper pour afficher un champ en lecture seule avec le style de l'ancien récap
|
||
Widget _buildDisplayFieldValue(
|
||
BuildContext context,
|
||
String label,
|
||
String value, {
|
||
bool multiLine = false,
|
||
double fieldHeight = 50.0,
|
||
double labelFontSize = 18.0,
|
||
double valueFontSize = 18.0, // Taille du texte dans le champ
|
||
}) {
|
||
const FontWeight labelFontWeight = FontWeight.w600;
|
||
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
label,
|
||
style: GoogleFonts.merienda(
|
||
fontSize: labelFontSize,
|
||
fontWeight: labelFontWeight,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Container(
|
||
width: double.infinity,
|
||
height: multiLine ? null : fieldHeight,
|
||
constraints: multiLine ? const BoxConstraints(minHeight: 50.0) : null,
|
||
padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0),
|
||
decoration: BoxDecoration(
|
||
image: const DecorationImage(
|
||
image: AssetImage('assets/images/bg_beige.png'),
|
||
fit: BoxFit.fill,
|
||
),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Text(
|
||
value.isNotEmpty ? value : '-',
|
||
style: GoogleFonts.merienda(fontSize: valueFontSize),
|
||
maxLines: multiLine ? null : 1,
|
||
overflow: multiLine ? TextOverflow.visible : TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// Layout MOBILE : tous les champs empilés verticalement
|
||
Widget _buildMobileFields(BuildContext context, DisplayConfig config) {
|
||
// Mode Readonly Mobile : Layout compact et groupé
|
||
if (config.isReadonly) {
|
||
// NOTE: FormFieldWrapper ajoute déjà un padding vertical (8px mobile),
|
||
// donc on n'ajoute pas de SizedBox supplémentaire ici pour éviter un double espacement.
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
_buildField(config: config, label: 'Nom', controller: _lastNameController),
|
||
_buildField(config: config, label: 'Prénom', controller: _firstNameController),
|
||
_buildField(config: config, label: 'Téléphone', controller: _phoneController),
|
||
_buildField(config: config, label: 'Email', controller: _emailController),
|
||
// Adresse complète en un seul bloc multiligne
|
||
FormFieldWrapper(
|
||
config: config,
|
||
label: 'Adresse',
|
||
value: "${_addressController.text}\n${_postalCodeController.text} ${_cityController.text}".trim(),
|
||
maxLines: 3,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
// Mode Editable Mobile : Layout standard avec champs séparés
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
// Nom
|
||
_buildField(
|
||
config: config,
|
||
label: 'Nom',
|
||
controller: _lastNameController,
|
||
hint: 'Votre nom de famille',
|
||
enabled: _fieldsEnabled,
|
||
focusOrder: _focusLastName,
|
||
focusNode: _lastNameFocus,
|
||
fieldValidator: widget.minPersonNameLength != null
|
||
? _validatePersonNameWithMinLength
|
||
: null,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// Prénom
|
||
_buildField(
|
||
config: config,
|
||
label: 'Prénom',
|
||
controller: _firstNameController,
|
||
hint: 'Votre prénom',
|
||
enabled: _fieldsEnabled,
|
||
focusOrder: _focusFirstName,
|
||
focusNode: _firstNameFocus,
|
||
fieldValidator: widget.minPersonNameLength != null
|
||
? _validatePersonNameWithMinLength
|
||
: null,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// Téléphone
|
||
_buildField(
|
||
config: config,
|
||
label: 'Téléphone',
|
||
controller: _phoneController,
|
||
hint: 'Votre numéro de téléphone',
|
||
keyboardType: TextInputType.phone,
|
||
enabled: _fieldsEnabled,
|
||
inputFormatters: frenchPhoneInputFormatters,
|
||
focusOrder: _focusPhone,
|
||
fieldValidator: _validateFrenchPhoneIfSecondParentNeeded,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// Email
|
||
_buildField(
|
||
config: config,
|
||
label: 'Email',
|
||
controller: _emailController,
|
||
hint: 'Votre adresse e-mail',
|
||
keyboardType: TextInputType.emailAddress,
|
||
enabled: _fieldsEnabled,
|
||
focusOrder: _focusEmail,
|
||
fieldValidator: _validateEmailIfSecondParentNeeded,
|
||
inputFormatters: const [EmailMaxLengthFormatter()],
|
||
focusNode: _emailFocus,
|
||
formFieldKey: _emailFormKey,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// Adresse
|
||
_buildField(
|
||
config: config,
|
||
label: 'Adresse (N° et Rue)',
|
||
controller: _addressController,
|
||
hint: 'Numéro et nom de votre rue',
|
||
enabled: _fieldsEnabled && !_sameAddress,
|
||
focusOrder: _focusAddress,
|
||
fieldValidator: widget.showSameAddressCheckbox
|
||
? _validateAddressLineIfManualEntry
|
||
: null,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// Code Postal
|
||
_buildField(
|
||
config: config,
|
||
label: 'Code Postal',
|
||
controller: _postalCodeController,
|
||
hint: '5 chiffres',
|
||
keyboardType: TextInputType.number,
|
||
enabled: _fieldsEnabled && !_sameAddress,
|
||
focusOrder: _focusPostal,
|
||
fieldValidator: _validatePostalCodeField,
|
||
inputFormatters: kFrenchPostalCodeInputFormatters,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// Ville
|
||
_buildField(
|
||
config: config,
|
||
label: 'Ville',
|
||
controller: _cityController,
|
||
hint: 'Votre ville',
|
||
enabled: _fieldsEnabled && !_sameAddress,
|
||
focusOrder: _focusCity,
|
||
focusNode: _cityFocus,
|
||
fieldValidator: widget.showSameAddressCheckbox
|
||
? _validateAddressLineIfManualEntry
|
||
: null,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// Construit un champ individuel (éditable ou readonly)
|
||
Widget _buildField({
|
||
required DisplayConfig config,
|
||
required String label,
|
||
required TextEditingController controller,
|
||
String? hint,
|
||
TextInputType? keyboardType,
|
||
bool enabled = true,
|
||
List<TextInputFormatter>? inputFormatters,
|
||
double? focusOrder,
|
||
FocusNode? focusNode,
|
||
GlobalKey<FormFieldState<String>>? formFieldKey,
|
||
String? Function(String?)? fieldValidator,
|
||
}) {
|
||
if (config.isReadonly) {
|
||
// Mode readonly : utiliser FormFieldWrapper (téléphone formaté pour affichage)
|
||
final displayValue = label == 'Téléphone' && controller.text.trim().isNotEmpty
|
||
? formatPhoneForDisplay(controller.text)
|
||
: controller.text;
|
||
return FormFieldWrapper(
|
||
config: config,
|
||
label: label,
|
||
value: displayValue,
|
||
);
|
||
} else {
|
||
final effectiveKeyboardType = keyboardType ?? TextInputType.text;
|
||
final isEmail = effectiveKeyboardType == TextInputType.emailAddress;
|
||
Widget field = CustomAppTextField(
|
||
formFieldKey: formFieldKey,
|
||
controller: controller,
|
||
focusNode: focusNode,
|
||
labelText: label,
|
||
hintText: hint ?? label,
|
||
style: CustomAppTextFieldStyle.beige,
|
||
fieldWidth: double.infinity,
|
||
fieldHeight: config.isMobile ? 45.0 : 53.0,
|
||
labelFontSize: config.isMobile ? 15.0 : 22.0,
|
||
inputFontSize: config.isMobile ? 14.0 : 20.0,
|
||
keyboardType: effectiveKeyboardType,
|
||
autocorrect: !isEmail,
|
||
enableSuggestions: !isEmail,
|
||
autofillHints: isEmail ? const [AutofillHints.email] : null,
|
||
textInputAction: isEmail ? TextInputAction.next : null,
|
||
enabled: enabled,
|
||
inputFormatters: inputFormatters,
|
||
validator: fieldValidator,
|
||
isRequired: fieldValidator == null,
|
||
);
|
||
if (focusOrder != null) {
|
||
field = FocusTraversalOrder(
|
||
order: NumericFocusOrder(focusOrder),
|
||
child: field,
|
||
);
|
||
}
|
||
return field;
|
||
}
|
||
}
|
||
|
||
static final _phoneInputFormatters = [
|
||
FilteringTextInputFormatter.digitsOnly,
|
||
LengthLimitingTextInputFormatter(10),
|
||
FrenchPhoneNumberFormatter(),
|
||
];
|
||
|
||
/// Retourne l'asset de carte vertical correspondant à la couleur
|
||
String _getVerticalCardAsset() {
|
||
switch (widget.cardColor) {
|
||
case CardColorHorizontal.blue:
|
||
return CardColorVertical.blue.path;
|
||
case CardColorHorizontal.green:
|
||
return CardColorVertical.green.path;
|
||
case CardColorHorizontal.lavender:
|
||
return CardColorVertical.lavender.path;
|
||
case CardColorHorizontal.lime:
|
||
return CardColorVertical.lime.path;
|
||
case CardColorHorizontal.peach:
|
||
return CardColorVertical.peach.path;
|
||
case CardColorHorizontal.pink:
|
||
return CardColorVertical.pink.path;
|
||
case CardColorHorizontal.red:
|
||
return CardColorVertical.red.path;
|
||
}
|
||
}
|
||
}
|