import 'package:flutter/material.dart'; import 'package:geosector_app/presentation/widgets/custom_text_field.dart'; import 'package:geosector_app/presentation/widgets/form_section.dart'; /// Exemple de validation avec CustomTextField /// Montre comment les erreurs sont gérées automatiquement class ValidationExample extends StatefulWidget { const ValidationExample({super.key}); @override State createState() => _ValidationExampleState(); } class _ValidationExampleState extends State { final _formKey = GlobalKey(); // Controllers final _numeroController = TextEditingController(); final _rueController = TextEditingController(); final _villeController = TextEditingController(); final _nameController = TextEditingController(); final _emailController = TextEditingController(); final _montantController = TextEditingController(); // FocusNodes pour contrôler le focus final _numeroFocus = FocusNode(); final _rueFocus = FocusNode(); final _villeFocus = FocusNode(); final _nameFocus = FocusNode(); final _emailFocus = FocusNode(); final _montantFocus = FocusNode(); @override void dispose() { _numeroController.dispose(); _rueController.dispose(); _villeController.dispose(); _nameController.dispose(); _emailController.dispose(); _montantController.dispose(); _numeroFocus.dispose(); _rueFocus.dispose(); _villeFocus.dispose(); _nameFocus.dispose(); _emailFocus.dispose(); _montantFocus.dispose(); super.dispose(); } /// Validation du formulaire avec focus automatique sur erreur void _validateForm() { // Form.validate() fait automatiquement : // 1. Valide tous les champs // 2. Affiche les erreurs (bordures rouges) // 3. Met le focus sur le PREMIER champ en erreur if (_formKey.currentState!.validate()) { // Formulaire valide ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Formulaire valide !'), backgroundColor: Colors.green, ), ); } else { // Des erreurs existent - le focus est déjà mis automatiquement ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Veuillez corriger les erreurs'), backgroundColor: Colors.red, ), ); } } /// Validation personnalisée pour email String? _validateEmail(String? value) { if (value == null || value.trim().isEmpty) { return null; // Email optionnel } const emailRegex = r'^[^@]+@[^@]+\.[^@]+$'; if (!RegExp(emailRegex).hasMatch(value)) { return 'Format email invalide'; } return null; } /// Validation pour montant String? _validateMontant(String? value) { if (value == null || value.trim().isEmpty) { return 'Le montant est obligatoire'; } final montant = double.tryParse(value.replaceAll(',', '.')); if (montant == null) { return 'Montant invalide'; } if (montant <= 0) { return 'Le montant doit être supérieur à 0'; } return null; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Exemple de Validation'), ), body: Form( // ← Important : wrapper Form key: _formKey, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: [ // Section Adresse FormSection( title: 'Adresse', icon: Icons.location_on, children: [ Row( children: [ Expanded( flex: 1, child: CustomTextField( controller: _numeroController, focusNode: _numeroFocus, label: "Numéro", isRequired: true, showLabel: false, textAlign: TextAlign.right, keyboardType: TextInputType.number, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Numéro obligatoire'; } final numero = int.tryParse(value); if (numero == null || numero <= 0) { return 'Numéro invalide'; } return null; }, ), ), const SizedBox(width: 12), const Expanded( flex: 1, child: CustomTextField( label: "Bis/Ter", showLabel: false, // Pas de validation - champ optionnel ), ), ], ), const SizedBox(height: 16), CustomTextField( controller: _rueController, focusNode: _rueFocus, label: "Rue", isRequired: true, showLabel: false, validator: (value) { if (value == null || value.trim().isEmpty) { return 'La rue est obligatoire'; } if (value.trim().length < 3) { return 'La rue doit contenir au moins 3 caractères'; } return null; }, ), const SizedBox(height: 16), CustomTextField( controller: _villeController, focusNode: _villeFocus, label: "Ville", isRequired: true, showLabel: false, validator: (value) { if (value == null || value.trim().isEmpty) { return 'La ville est obligatoire'; } return null; }, ), ], ), const SizedBox(height: 24), // Section Occupant FormSection( title: 'Occupant', icon: Icons.person, children: [ CustomTextField( controller: _nameController, focusNode: _nameFocus, label: "Nom de l'occupant", isRequired: true, showLabel: false, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Le nom est obligatoire pour les passages effectués'; } if (value.trim().length < 2) { return 'Le nom doit contenir au moins 2 caractères'; } return null; }, ), const SizedBox(height: 16), CustomTextField( controller: _emailController, focusNode: _emailFocus, label: "Email", showLabel: false, keyboardType: TextInputType.emailAddress, validator: _validateEmail, ), ], ), const SizedBox(height: 24), // Section Règlement FormSection( title: 'Règlement', icon: Icons.euro, children: [ CustomTextField( controller: _montantController, focusNode: _montantFocus, label: "Montant (€)", isRequired: true, showLabel: false, hintText: "0.00", textAlign: TextAlign.right, keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: _validateMontant, ), ], ), const SizedBox(height: 32), // Boutons de test Column( children: [ ElevatedButton.icon( onPressed: _validateForm, icon: const Icon(Icons.check), label: const Text('Valider le formulaire'), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, minimumSize: const Size(200, 48), ), ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: () { // Effacer le formulaire _formKey.currentState?.reset(); _numeroController.clear(); _rueController.clear(); _villeController.clear(); _nameController.clear(); _emailController.clear(); _montantController.clear(); }, icon: const Icon(Icons.clear), label: const Text('Effacer'), ), ], ), const SizedBox(height: 24), // Info box Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3), borderRadius: BorderRadius.circular(8), border: Border.all( color: Theme.of(context).colorScheme.primary.withOpacity(0.3), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.info_outline, color: Theme.of(context).colorScheme.primary, size: 20, ), const SizedBox(width: 8), Text( 'Test de validation', style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary, ), ), ], ), const SizedBox(height: 8), const Text( '• Laissez des champs obligatoires vides et cliquez "Valider"\n' '• Les bordures deviennent rouges automatiquement\n' '• Le focus se met sur le premier champ en erreur\n' '• Les messages d\'erreur s\'affichent sous les champs', ), ], ), ), ], ), ), ), ); } }