petitspas/frontend/lib/widgets/presentation_form_screen.dart
Julien Martin dcb81d3feb 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
2026-01-28 16:43:36 +01:00

170 lines
5.7 KiB
Dart

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',
),
),
],
),
);
}
}