feat(widgets): Création de widgets génériques réutilisables
Création de 2 nouveaux widgets génériques pour réduire la duplication : 1. presentation_form_screen.dart - Widget pour formulaires de présentation/motivation - Paramétrable : titre, couleur, hint, routes - Utilisé par Parent Step 4 et AM Step 3 - Réduction de ~350 lignes de code dupliqué 2. personal_info_form_screen.dart - Widget pour formulaires d'informations personnelles - Gère nom, prénom, téléphone, email, adresse - Options : toggle "2ème parent", checkbox "même adresse" - Utilisé par Parent Steps 1-2 et AM Step 1 - Réduction de ~460 lignes de code dupliqué Avantages : - Maintenance simplifiée (1 seul fichier à modifier) - Cohérence visuelle garantie entre tous les écrans - Extensibilité facile pour nouveaux types d'utilisateurs
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'custom_decorated_text_field.dart';
|
||||
import 'app_custom_checkbox.dart';
|
||||
import '../models/card_assets.dart';
|
||||
|
||||
class PresentationFormScreen extends StatefulWidget {
|
||||
final String stepText; // Ex: "Étape 3/4" ou "Étape 4/5"
|
||||
final String title; // Ex: "Présentation et Conditions" ou "Motivation de votre demande"
|
||||
final CardColorHorizontal cardColor;
|
||||
final String textFieldHint;
|
||||
final String initialText;
|
||||
final bool initialCguAccepted;
|
||||
final String previousRoute;
|
||||
final Function(String text, bool cguAccepted) onSubmit;
|
||||
|
||||
const PresentationFormScreen({
|
||||
super.key,
|
||||
required this.stepText,
|
||||
required this.title,
|
||||
required this.cardColor,
|
||||
required this.textFieldHint,
|
||||
required this.initialText,
|
||||
required this.initialCguAccepted,
|
||||
required this.previousRoute,
|
||||
required this.onSubmit,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PresentationFormScreen> createState() => _PresentationFormScreenState();
|
||||
}
|
||||
|
||||
class _PresentationFormScreenState extends State<PresentationFormScreen> {
|
||||
late TextEditingController _textController;
|
||||
late bool _cguAccepted;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textController = TextEditingController(text: widget.initialText);
|
||||
_cguAccepted = widget.initialCguAccepted;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleSubmit() {
|
||||
if (!_cguAccepted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Vous devez accepter les CGU pour continuer.'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
widget.onSubmit(_textController.text, _cguAccepted);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
final cardWidth = screenSize.width * 0.6;
|
||||
final double imageAspectRatio = 2.0;
|
||||
final cardHeight = cardWidth / imageAspectRatio;
|
||||
|
||||
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, horizontal: 50.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.stepText,
|
||||
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
widget.title,
|
||||
style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Container(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(widget.cardColor.path),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomDecoratedTextField(
|
||||
controller: _textController,
|
||||
hintText: widget.textFieldHint,
|
||||
fieldHeight: cardHeight * 0.6,
|
||||
maxLines: 10,
|
||||
expandDynamically: true,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
AppCustomCheckbox(
|
||||
label: 'J\'accepte les Conditions Générales\nd\'Utilisation et la Politique de confidentialité',
|
||||
value: _cguAccepted,
|
||||
onChanged: (value) => setState(() => _cguAccepted = value ?? false),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 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(widget.previousRoute);
|
||||
}
|
||||
},
|
||||
tooltip: 'Retour',
|
||||
),
|
||||
),
|
||||
// Chevron Droit (Suivant)
|
||||
Positioned(
|
||||
top: screenSize.height / 2 - 20,
|
||||
right: 40,
|
||||
child: IconButton(
|
||||
icon: Image.asset('assets/images/chevron_right.png', height: 40),
|
||||
onPressed: _cguAccepted ? _handleSubmit : null,
|
||||
tooltip: 'Suivant',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user