Files
geo/app/lib/presentation/widgets/validation_example.dart
Pierre f7baa7492c feat: Mise à jour des interfaces mobiles v3.2.3
- Amélioration des interfaces utilisateur sur mobile
- Optimisation de la responsivité des composants Flutter
- Mise à jour des widgets de chat et communication
- Amélioration des formulaires et tableaux
- Ajout de nouveaux composants pour l'administration
- Optimisation des thèmes et styles visuels

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 20:35:40 +02:00

327 lines
12 KiB
Dart
Executable File

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<ValidationExample> createState() => _ValidationExampleState();
}
class _ValidationExampleState extends State<ValidationExample> {
final _formKey = GlobalKey<FormState>();
// 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.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.primary.withValues(alpha: 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',
),
],
),
),
],
),
),
),
);
}
}