- 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>
61 KiB
Executable File
GEOSECTOR v2.1
🚒 Application de gestion des distributions de calendriers par secteurs géographiques pour les amicales de pompiers
🎯 Vue d'ensemble
GEOSECTOR est une solution complète développée en Flutter qui révolutionne la gestion des campagnes de distribution de calendriers pour les amicales de pompiers. L'application combine géolocalisation, gestion multi-rôles et synchronisation en temps réel pour optimiser les tournées et maximiser l'efficacité des équipes.
🏆 Points forts de la v2.1
- Architecture moderne sans Provider, basée sur l'injection de dépendances
- Réactivité native avec ValueListenableBuilder et Hive
- Interface adaptative selon les rôles utilisateur et la taille d'écran
- Performance optimisée avec un ApiService singleton
- Gestion avancée des permissions multi-niveaux
- Gestion d'erreurs centralisée avec ApiException
- Interface utilisateur épurée avec suppression des titres superflus
- Chat responsive avec layout adaptatif mobile/desktop
📋 Table des matières
- Fonctionnalités
- Architecture technique
- Installation
- Modèles de données
- Architecture des composants
- Gestion des rôles
- Interface utilisateur
- API et synchronisation
- Gestion des erreurs
- Cartes et géolocalisation
🚀 Fonctionnalités
🎯 Fonctionnalités métier
Pour les Membres (Rôle 1)
- ✅ Visualisation des secteurs assignés sur carte interactive
- ✅ Suivi GPS en temps réel des tournées
- ✅ Enregistrement des passages avec géolocalisation
- ✅ Gestion des stocks de calendriers
- ✅ Historique des distributions
- ✅ Chat intégré avec l'équipe
Pour les Admins Amicale (Rôle 2)
- ✅ Gestion de leur amicale (informations, coordonnées)
- ✅ Gestion des membres de l'amicale (création, modification, suppression)
- ✅ Attribution des rôles aux membres (Membre/Administrateur)
- ✅ Gestion du statut actif des comptes membres
- ✅ Consultation des statistiques de l'amicale
- ✅ Attribution des secteurs aux membres
- ✅ Suivi des performances équipe
Pour les Super Admins (Rôle 3+)
- ✅ Gestion globale multi-amicales
- ✅ Administration des utilisateurs et permissions
- ✅ Configuration des paramètres système
- ✅ Analytics avancées et reporting
- ✅ Gestion des secteurs géographiques
🔧 Fonctionnalités techniques
- 🗺️ Cartographie avancée : Flutter Map avec tuiles Mapbox
- 📍 Géolocalisation précise : Suivi GPS des équipes
- 💾 Stockage hybride : Cache local Hive + synchronisation cloud avec optimisation des performances
- 💬 Communication : Chat MQTT en temps réel
- 🔐 Sécurité : Authentification JWT + gestion fine des permissions
- 📱 Multi-plateforme : iOS, Android, Web
- 🌐 Mode hors-ligne : Fonctionnement dégradé sans connexion
- ⚡ Gestion d'erreurs robuste : Extraction automatique des messages API
- 🎨 Interface responsive : Adaptation automatique selon la taille d'écran
🏗️ Architecture technique
Stack technologique
| Composant | Technologie | Version | Usage |
|---|---|---|---|
| Framework | Flutter | 3.32+ | Interface multi-plateforme |
| Langage | Dart | 3.0+ | Logique applicative |
| Navigation | GoRouter | 12.1.3 | Routing déclaratif |
| Stockage local | Hive | 2.2.3 | Base de données NoSQL locale |
| Réactivité | ValueListenableBuilder | Native | Écoute des changements Hive |
| HTTP | Dio | 5.4.0 | Client HTTP avec intercepteurs |
| Cartes | Flutter Map | 6.1.0 | Rendu cartographique |
| Géolocalisation | Geolocator | 10.1.0 | Services de localisation |
| Chat | MQTT5 Client | 4.2.0 | Messagerie temps réel |
| UI | Material Design 3 | Native | Composants d'interface |
| Logging | LoggerService | Custom | Logs conditionnels par env |
🏛️ Architecture en couches
graph TD
A[UI Layer - Widgets] --> B[Repository Layer - Business Logic]
B --> C[Data Layer - Hive + API]
A1[ValueListenableBuilder] --> A
A2[Custom Widgets] --> A
A3[UserFormDialog] --> A
B1[UserRepository] --> B
B2[AmicaleRepository] --> B
B3[MembreRepository] --> B
C1[Hive Boxes] --> C
C2[API Service Singleton] --> C
C3[ApiException Handler] --> C
📁 Structure du projet
app/
├── lib/
│ ├── core/ # Couche centrale
│ │ ├── constants/ # Constantes globales
│ │ │ ├── app_keys.dart # Clés des Box Hive
│ │ │ └── api_endpoints.dart # Endpoints API
│ │ ├── data/
│ │ │ └── models/ # Modèles Hive
│ │ │ ├── user_model.dart # @HiveType(typeId: 3)
│ │ │ ├── amicale_model.dart # @HiveType(typeId: 4)
│ │ │ └── membre_model.dart # @HiveType(typeId: 5)
│ │ ├── repositories/ # Logique métier
│ │ │ ├── user_repository.dart
│ │ │ ├── amicale_repository.dart
│ │ │ └── membre_repository.dart
│ │ ├── services/ # Services externes
│ │ │ ├── api_service.dart # HTTP Singleton
│ │ │ ├── chat_service.dart # MQTT
│ │ │ └── location_service.dart # GPS
│ │ └── utils/ # Utilitaires
│ │ ├── validators.dart
│ │ └── formatters.dart
│ ├── presentation/ # Interface utilisateur
│ │ ├── admin/ # Pages administrateur
│ │ │ ├── admin_dashboard_page.dart
│ │ │ ├── admin_amicale_page.dart
│ │ │ └── admin_statistics_page.dart
│ │ ├── user/ # Pages utilisateur
│ │ │ ├── user_dashboard_page.dart
│ │ │ ├── map_page.dart
│ │ │ └── distribution_page.dart
│ │ ├── widgets/ # Composants réutilisables
│ │ │ ├── tables/
│ │ │ │ ├── amicale_table_widget.dart
│ │ │ │ ├── amicale_row_widget.dart
│ │ │ │ ├── membre_table_widget.dart
│ │ │ │ └── membre_row_widget.dart
│ │ │ ├── forms/
│ │ │ │ ├── amicale_form.dart
│ │ │ │ └── custom_text_field.dart
│ │ │ └── common/
│ │ │ ├── dashboard_layout.dart
│ │ │ └── loading_widget.dart
│ │ └── theme/
│ │ └── app_theme.dart
│ ├── app.dart # Configuration app
│ └── main.dart # Point d'entrée
├── assets/ # Ressources statiques
│ ├── images/
│ ├── icons/
│ └── fonts/
├── test/ # Tests unitaires
├── integration_test/ # Tests d'intégration
└── docs/ # Documentation
🚀 Installation et configuration
Prérequis système
- Flutter SDK : 3.32 ou supérieur
- Dart SDK : 3.0 ou supérieur
- IDE : Android Studio, VS Code, ou IntelliJ
- Environnement :
- Android : SDK 21+ (Android 5.0+)
- iOS : iOS 12.0+
- Web : Navigateurs modernes
🔐 Configuration des clés API
Mapbox (Cartographie)
- Créer un compte sur Mapbox
- Générer un token d'accès
- Ajouter le token dans
.env
Configuration MQTT (Chat)
- Configurer votre broker MQTT
- Créer les credentials
- Tester la connexion
🗄️ Modèles de données
Registres Hive des adaptateurs
// Modèles principaux
UserModelAdapter() // typeId: 0
OperationModelAdapter() // typeId: 1
SectorModelAdapter() // typeId: 3
PassageModelAdapter() // typeId: 4
MembreModelAdapter() // typeId: 5
UserSectorModelAdapter() // typeId: 6
RegionModelAdapter() // typeId: 7
ClientModelAdapter() // typeId: 10
AmicaleModelAdapter() // typeId: 11
// Modèles de chat
ConversationModelAdapter() // typeId: 20
MessageModelAdapter() // typeId: 21
ParticipantModelAdapter() // typeId: 22
AnonymousUserModelAdapter() // typeId: 23
AudienceTargetModelAdapter() // typeId: 24
NotificationSettingsAdapter() // typeId: 25
Clarification importante : UserModel vs MembreModel vs UserSectorModel
⚠️ ATTENTION : Il existe une distinction cruciale entre ces trois modèles :
UserModel (Box: users)
- Représente uniquement l'utilisateur courant connecté (current user)
- Stocké dans la box Hive
usersqui ne contient qu'un seul enregistrement - Utilisé pour l'authentification et la session de l'utilisateur actuel
- Ne pas confondre avec les membres de l'amicale
MembreModel (Box: membres)
- Représente tous les membres d'une amicale
- Stocké dans la box Hive
membresqui contient plusieurs enregistrements - Utilisé pour la gestion des équipes et l'attribution aux secteurs
- Chaque membre a son propre ID unique
UserSectorModel (Box: user_sector)
- Représente l'association entre un membre et un secteur
- ⚠️ IMPORTANT : Le champ
iddansUserSectorModelcorrespond à l'ID du membre (MembreModel.id), PAS à l'ID de l'utilisateur (UserModel.id) - Permet de savoir quels membres sont affectés à quels secteurs
- Nom trompeur : devrait s'appeler "MemberSectorModel" pour éviter la confusion
Compatibilité entre modèles
- UserModel ↔ MembreModel : Conversion bidirectionnelle via
toUserModel()etfromUserModel() - Synchronisation : Maintien de la cohérence entre les deux représentations
- Champs spécialisés : Préservation des données spécifiques à chaque modèle
🎨 Interface utilisateur
📱 Améliorations v2.1 - Interface épurée et responsive
🎯 Simplification des titres de pages
La v2.1 a apporté une refonte majeure de l'interface pour maximiser l'espace utile et améliorer l'expérience utilisateur sur tous les écrans :
Pages avec titres supprimés :
- ✅
user_history_page.dart: Historique des passages - ✅
user_statistics_page.dart: Statistiques - ✅
user_map_page.dart: Carte des passages - ✅
admin_history_page.dart: Historique admin - ✅
admin_statistics_page.dart: Statistiques admin - ✅
chat_communication_page.dart: Interface de chat
Pages avec titres conservés mais optimisés :
- ✅
user_dashboard_home_page.dart: Titre responsive (taille réduite de 28 à 20) - ✅
admin_dashboard_home_page.dart: Titre réduit (de headlineSmall à titleLarge) + suppression icône refresh
💬 Chat responsive adaptatif
Le module de chat (rooms_page_embedded.dart) s'adapte automatiquement à la taille d'écran :
Desktop (>900px) :
- Layout horizontal : Rooms à gauche (300px), Messages à droite
- Navigation fluide entre les conversations
Mobile (<900px) :
- Layout vertical : Rooms en haut (30% hauteur), Messages en bas
- Hauteur adaptative avec contraintes (200-350px)
- Optimisation pour les écrans tactiles
🗺️ Carte avec filtres intégrés
La carte des passages (user_map_page.dart) a été repensée :
- Carte plein écran : Utilisation maximale de l'espace disponible
- Filtres en overlay : 6 pastilles colorées en bas à gauche
- Design minimaliste :
- Pastille vive = filtre actif
- Pastille semi-transparente (alpha 0.3) = filtre inactif
- Sans labels pour économiser l'espace
- Container blanc arrondi avec ombre pour regrouper les pastilles
Architecture des composants
UserFormDialog - Modale unifiée Réutilisabilité : Même widget pour "Mon Compte" et "Gestion des Membres" Personnalisation contextuelle : Sélecteur de rôle (Membre/Administrateur) Checkbox statut actif/inactif Édition du nom d'utilisateur selon le contexte Gestion du nom de tournée (sectName) Interface responsive : Adaptation automatique selon la largeur d'écran UserForm - Formulaire intelligent Layout adaptatif :
900px : Champs groupés en lignes (username+email, prénom+nom, téléphones, dates) ≤ 900px : Champs empilés verticalement Validation conditionnelle : Au moins nom OU nom de tournée requis Champs dynamiques : Affichage selon les permissions et le contexte Indicateurs visuels : Points rouges sur les champs obligatoires Tableaux interactifs AmicaleTableWidget : Gestion des amicales avec édition inline MembreTableWidget : Gestion des membres avec actions contextuelles Alternance de couleurs : Amélioration de la lisibilité Clic sur ligne : Ouverture directe du formulaire d'édition
🔗 API et synchronisation
Principe "API First"
Flow de mise à jour
Validation API : Tentative de mise à jour sur le serveur Succès → Sauvegarde locale avec isSynced: true Erreur → Aucune modification locale + affichage de l'erreur
Avantages
Cohérence des données : Local toujours synchronisé avec le serveur Gestion d'erreurs propre : Pas de conflits entre données locales et distantes UX claire : Feedback immédiat sur les erreurs de validation
ApiService Singleton
- Thread-safe : Initialisation sécurisée avec verrous
- Auto-configuration : Détection automatique de l'environnement (DEV/REC/PROD)
- Gestion de session : Headers d'authentification automatiques
- Retry logic : Nouvelles tentatives pour les erreurs réseau
⚠️ Gestion des erreurs
🎯 Système ApiException intelligent
GEOSECTOR v2.0 utilise un système centralisé de gestion des messages qui s'adapte automatiquement au contexte d'affichage pour garantir une visibilité optimale des notifications utilisateur.
🧠 Détection automatique de contexte
L'ApiException détecte intelligemment si elle est appelée depuis une Dialog et adapte l'affichage :
- 📱 Contexte normal : SnackBar standard en bas d'écran
- 💬 Contexte Dialog : Overlay SnackBar positionné au-dessus de la Dialog estompée
- 🌐 Mobile/Web : Adaptation automatique selon la plateforme
- 🎨 Cohérence visuelle : Couleurs, icônes et comportements unifiés
// Même API partout - détection intelligente du contexte
void _handleValidation() {
if (formInvalid) {
// Dans une Dialog : overlay au-dessus, Dialog reste ouverte
ApiException.showError(context, Exception("Champs requis manquants"));
return;
}
// Succès : fermer Dialog puis afficher confirmation
Navigator.pop(context);
ApiException.showSuccess(context, "Données sauvegardées");
}
✨ Avantages de l'approche unifiée
| Aspect | Avant | Avec ApiException |
|---|---|---|
| Visibilité | SnackBar masqué par Dialog | Overlay visible au-dessus |
| Consistance | Messages dispersés | API unifiée dans toute l'app |
| Maintenance | Code répétitif | Système centralisé |
| UX | Frustrant (messages cachés) | Fluide et prévisible |
Architecture centralisée
sequenceDiagram
participant UI as dashboard_app_bar.dart
participant UR as user_repository.dart
participant AS as api_service.dart
participant API as API Server
participant AE as ApiException
participant EU as ErrorUtils
Note over UI: Utilisateur clique "Enregistrer"
UI->>UR: updateUser(updatedUser)
Note over UR: Tente la mise à jour
UR->>AS: updateUser(user)
Note over AS: Appel HTTP PUT
AS->>API: PUT /users/123 {email: "test@test.com"}
alt Succès API
API-->>AS: 200 OK {user data}
AS-->>UR: UserModel (mis à jour)
UR-->>UI: UserModel (succès)
UI->>EU: showSuccessSnackBar()
Note over UI: ✅ "Profil mis à jour"
else Erreur API (ex: email déjà utilisé)
API-->>AS: 409 Conflict {"message": "Cet email est déjà utilisé"}
Note over AS: Conversion en ApiException
AS->>AE: ApiException.fromDioException(dioError)
AE-->>AS: ApiException("Cet email est déjà utilisé")
AS-->>UR: throw ApiException
UR-->>UI: throw ApiException
Note over UI: Gestion de l'erreur
UI->>EU: showErrorSnackBar(context, exception)
EU->>AE: extractErrorMessage(exception)
AE-->>EU: "Cet email est déjà utilisé"
Note over UI: ❌ "Erreur: Cet email est déjà utilisé"
Note over UI: Dialog reste ouvert
else Erreur réseau
API-->>AS: Network Error / Timeout
AS->>AE: ApiException.fromDioException(networkError)
AE-->>AS: ApiException("Problème de connexion réseau")
AS-->>UR: throw ApiException
UR-->>UI: throw ApiException
UI->>EU: showErrorSnackBar(context, exception)
Note over UI: ❌ "Problème de connexion réseau"
end
Composants de gestion d'erreurs
ApiException
Extraction intelligente : Messages spécifiques depuis la réponse API Codes HTTP standardisés : Mapping automatique des erreurs communes Types d'erreurs : Classification (validation, authentification, réseau, conflit) Méthodes d'affichage : showError() et showSuccess() intégrées
Responsabilités par couche
ApiService : Conversion des erreurs Dio en ApiException Repository : Propagation transparente des erreurs Interface : Affichage utilisateur via ApiException.showError()
Messages d'erreurs contextuels
409 Conflict : "Cet email est déjà utilisé par un autre utilisateur" 400 Bad Request : "Données invalides" 401 Unauthorized : "Non autorisé : veuillez vous reconnecter" 500 Server Error : "Erreur serveur interne" Network Errors : "Problème de connexion réseau" Timeout : "Délai d'attente dépassé"
🔧 Pattern de gestion des erreurs API dans les Repositories
🎯 Problème à résoudre
Les messages d'erreur spécifiques de l'API (comme "Cet email est déjà utilisé") n'étaient pas affichés à l'utilisateur. À la place, un message générique "Erreur inattendue" apparaissait.
📝 Modifications requises
1. ApiService - Conversion automatique des DioException
Toutes les méthodes HTTP génériques doivent convertir les DioException en ApiException :
// ✅ CORRECT - ApiService avec conversion automatique
Future<Response> put(String path, {dynamic data}) async {
try {
return await _dio.put(path, data: data);
} on DioException catch (e) {
throw ApiException.fromDioException(e); // ← Extraction automatique du message
} catch (e) {
if (e is ApiException) rethrow;
throw ApiException('Erreur inattendue lors de la requête PUT', originalError: e);
}
}
Appliquer le même pattern pour post(), get(), delete().
2. Repository - Simplification de la gestion des erreurs
// ❌ INCORRECT - Code inutile qui ne sera jamais exécuté
Future<bool> updateMembre(MembreModel membre) async {
try {
final response = await ApiService.instance.put('/users/${membre.id}', data: data);
if (response.statusCode == 200) {
await saveMembreBox(membre);
return true;
}
// ⚠️ CE CODE NE SERA JAMAIS ATTEINT car Dio lance une exception pour les codes d'erreur
if (response.data != null && response.data is Map<String, dynamic>) {
final responseData = response.data as Map<String, dynamic>;
if (responseData['status'] == 'error') {
throw Exception(responseData['message']);
}
}
return false;
} catch (e) {
rethrow;
}
}
// ✅ CORRECT - Code simplifié et fonctionnel
Future<bool> updateMembre(MembreModel membre) async {
try {
final response = await ApiService.instance.put('/users/${membre.id}', data: data);
// Si on arrive ici, c'est que la requête a réussi (200)
await saveMembreBox(membre);
return true;
} catch (e) {
// L'ApiException contient déjà le message extrait de l'API
rethrow; // Propager l'exception pour affichage
}
}
3. Amélioration des logs (avec LoggerService)
// ✅ CORRECT - Logs propres sans détails techniques
catch (e) {
// Ne pas logger les détails techniques de DioException
if (e is ApiException) {
LoggerService.error('Erreur lors de la mise à jour: ${e.message}');
} else {
LoggerService.error('Erreur lors de la mise à jour');
}
rethrow;
}
N'oubliez pas d'importer ApiException :
import 'package:geosector_app/core/utils/api_exception.dart';
🔄 Flux d'erreur corrigé
sequenceDiagram
participant API as API Server
participant Dio as Dio Client
participant AS as ApiService
participant AE as ApiException
participant R as Repository
participant UI as Interface
UI->>R: updateData()
R->>AS: put('/endpoint')
AS->>Dio: HTTP PUT
Dio->>API: Request
alt Code d'erreur (409, 400, etc.)
API-->>Dio: Error + JSON body
Note over API: {"status": "error",<br/>"message": "Message spécifique"}
Dio-->>AS: DioException
AS->>AE: fromDioException()
Note over AE: Extrait message<br/>depuis response.data
AE-->>AS: ApiException("Message spécifique")
AS-->>R: throw ApiException
R-->>UI: throw ApiException
UI->>UI: showError("Message spécifique")
else Succès (200, 201)
API-->>Dio: Success
Dio-->>AS: Response
AS-->>R: Response
R->>R: Sauvegarde Hive
R-->>UI: return true
end
✅ Checklist de migration
Pour chaque repository :
- Vérifier que l'ApiService convertit les DioException en ApiException
- Simplifier le code : supprimer les vérifications de statut après l'appel API
- Propager les exceptions avec
rethrow - Améliorer les logs pour ne pas afficher les détails techniques
- Importer
ApiExceptionsi nécessaire - Tester avec une erreur 409 pour vérifier l'affichage du message
📊 Résultat attendu
| Avant | Après |
|---|---|
| "Erreur inattendue" | "Cet email est déjà utilisé par un autre utilisateur" |
| Logs avec stack trace Dio | Message d'erreur simple et clair |
| Code complexe avec vérifications inutiles | Code simplifié et maintenable |
Cette approche garantit que tous les messages d'erreur de l'API sont correctement affichés à l'utilisateur, améliorant ainsi l'expérience utilisateur et facilitant le débogage.
📝 Service de Logging Intelligent
🎯 Vue d'ensemble
GEOSECTOR v2.0 implémente un LoggerService centralisé qui désactive automatiquement les logs de debug en production, optimisant ainsi les performances et la sécurité tout en facilitant le développement.
🔍 Détection automatique de l'environnement
Le LoggerService détecte automatiquement l'environnement d'exécution :
// Détection basée sur l'URL pour le web
if (currentUrl.contains('dapp.geosector.fr')) → DEV
if (currentUrl.contains('rapp.geosector.fr')) → REC
Sinon → PROD
// Pour mobile/desktop : utilise kReleaseMode de Flutter
Comportement par environnement :
| Environnement | Logs Debug | Logs Erreur | Stack Traces |
|---|---|---|---|
| DEV | ✅ Activés | ✅ Activés | ✅ Complètes |
| REC | ✅ Activés | ✅ Activés | ✅ Complètes |
| PROD | ❌ Désactivés | ✅ Activés | ❌ Masquées |
🛠️ API du LoggerService
Méthodes principales
// Remplacement direct de debugPrint
LoggerService.log('Message simple');
// Logs catégorisés avec emojis automatiques
LoggerService.info('Information'); // ℹ️
LoggerService.success('Opération réussie'); // ✅
LoggerService.warning('Attention'); // ⚠️
LoggerService.error('Erreur', exception); // ❌ (toujours affiché)
LoggerService.debug('Debug', emoji: '🔧'); // 🔧
// Logs spécialisés
LoggerService.api('Requête API envoyée'); // 🔗
LoggerService.database('Box Hive ouverte'); // 💾
LoggerService.navigation('Route: /admin'); // 🧭
LoggerService.performance('Temps: 45ms'); // ⏱️
Fonctionnalités avancées
// Logs groupés pour améliorer la lisibilité
LoggerService.group('Traitement utilisateur', [
'Validation des données',
'Appel API',
'Sauvegarde locale',
'Notification envoyée'
]);
// Affiche :
// ┌─ Traitement utilisateur
// ├─ Validation des données
// ├─ Appel API
// ├─ Sauvegarde locale
// └─ Notification envoyée
// Log JSON formaté
LoggerService.json('Payload API', {
'id': 123,
'name': 'Test',
'active': true
});
// Affiche :
// 📋 Payload API:
// id: 123
// name: Test
// active: true
// Log conditionnel
LoggerService.conditional(
'Debug spécifique',
condition: user.role > 2
);
🔄 Migration depuis debugPrint
Avant (debugPrint partout)
debugPrint('🔄 Début de la création d\'un nouveau membre');
debugPrint('📤 Données envoyées à l\'API: $data');
debugPrint('❌ Erreur: $e');
Après (LoggerService)
LoggerService.info('Début de la création d\'un nouveau membre');
LoggerService.api('Données envoyées à l\'API: $data');
LoggerService.error('Erreur lors de la création', e);
📋 Utilisation avec les extensions
Pour une syntaxe encore plus concise, utilisez les extensions String :
// Import nécessaire
import 'package:geosector_app/core/services/logger_service.dart';
// Utilisation directe sur les strings
'Connexion réussie'.logSuccess();
'Erreur de validation'.logError(exception);
'Requête GET /users'.logApi();
'Box membres ouverte'.logDatabase();
🎯 Bonnes pratiques
1. Catégorisation des logs
// ✅ BON - Log catégorisé et clair
LoggerService.api('POST /users - Création membre');
LoggerService.database('Sauvegarde dans Box membres');
// ❌ MAUVAIS - Log générique sans contexte
debugPrint('Traitement en cours...');
2. Gestion des erreurs
try {
await operation();
LoggerService.success('Opération terminée');
} catch (e, stackTrace) {
// Les erreurs sont TOUJOURS loggées, même en PROD
LoggerService.error('Échec de l\'opération', e, stackTrace);
}
3. Logs de performance
final stopwatch = Stopwatch()..start();
await heavyOperation();
stopwatch.stop();
LoggerService.performance('Opération lourde: ${stopwatch.elapsedMilliseconds}ms');
🔒 Sécurité et performances
Avantages en production
- Sécurité : Aucune information sensible exposée dans la console
- Performance : Pas d'appels inutiles à debugPrint
- Taille : Bundle JavaScript plus léger (tree shaking)
- Professionnalisme : Console propre pour les utilisateurs finaux
Conservation des logs d'erreur
Les erreurs restent visibles en production pour faciliter le support :
// Toujours affiché, même en PROD
LoggerService.error('Erreur critique détectée', error);
// Mais sans la stack trace complète qui pourrait révéler
// des détails d'implémentation
📊 Impact sur le codebase
| Métrique | Avant | Après |
|---|---|---|
| Logs en PROD | Tous visibles | Erreurs uniquement |
| Performance web | debugPrint actifs | Désactivés automatiquement |
| Maintenance | debugPrint dispersés | Service centralisé |
| Lisibilité | Emojis manuels | Catégorisation automatique |
🚀 Configuration et initialisation
Le LoggerService fonctionne automatiquement sans configuration :
// main.dart - Aucune initialisation requise
void main() async {
// LoggerService détecte automatiquement l'environnement
// via ApiService.getCurrentEnvironment()
runApp(GeosectorApp());
}
// Utilisation immédiate dans n'importe quel fichier
LoggerService.info('Application démarrée');
Cette architecture garantit un système de logging professionnel, sécurisé et performant, adapté aux besoins de développement tout en protégeant la production. 📝✨
🔐 Gestion des identifiants - Normes NIST SP 800-63B
🎯 Vue d'ensemble
GEOSECTOR v2.0 implémente les normes NIST SP 800-63B pour la gestion des identifiants (usernames et passwords), offrant une sécurité renforcée tout en améliorant l'expérience utilisateur avec des règles plus flexibles et modernes.
📋 Conformité NIST SP 800-63B
| Exigence NIST | Implémentation | Statut |
|---|---|---|
| Longueur minimale : 8 caractères | ✅ MIN = 8 caractères | ✅ CONFORME |
| Longueur maximale : 64 caractères minimum | ✅ MAX = 64 caractères | ✅ CONFORME |
| Accepter TOUS les caractères ASCII imprimables | ✅ Aucune restriction sur les caractères | ✅ CONFORME |
| Accepter les espaces | ✅ Espaces acceptés (début, milieu, fin) | ✅ CONFORME |
| Accepter Unicode (émojis, accents, etc.) | ✅ Support UTF-8 complet | ✅ CONFORME |
| Vérifier contre les mots de passe compromis | ✅ API Have I Been Pwned avec k-anonymity | ✅ CONFORME |
| Pas d'obligation de composition | ✅ Pas d'erreur si manque majuscules/chiffres/spéciaux | ✅ CONFORME |
| Pas de changement périodique forcé | ✅ Aucune expiration automatique | ✅ CONFORME |
| Permettre les phrases de passe | ✅ "Mon chat Félix a 3 ans!" accepté | ✅ CONFORME |
🔑 Règles pour les identifiants
Username (Nom d'utilisateur)
- Longueur : 8 à 64 caractères
- Caractères acceptés : TOUS (lettres, chiffres, espaces, accents, symboles, emojis)
- Exemples valides :
jean dupont 75Marie-Claire.2024Pompier Paris 🚒utilisateur@amicale
Password (Mot de passe)
- Longueur : 8 à 64 caractères
- Aucune règle de composition obligatoire (plus besoin de majuscules/minuscules/chiffres/spéciaux)
- Phrases de passe recommandées pour une meilleure mémorisation
- Exemples valides :
Mon chat Félix a 3 ans!J'aime les pizzas du vendredi soirLe camion rouge part à 8h30☀️ Soleil brillant sur Paris ☀️
🎲 Générateurs intelligents
Générateur de username
Crée des noms d'utilisateur uniques basés sur :
- Nom/prénom de la personne
- Code postal et ville de l'amicale
- Numéro aléatoire pour l'unicité
- Peut inclure des espaces et séparateurs (., -, _)
Générateur de phrases de passe
Génère des phrases de passe mémorables en français :
- Phrases naturelles et faciles à retenir
- Combinaisons variées (sujets, verbes, compléments)
- Ajout optionnel de caractères spéciaux ou emojis
- Exemples générés :
Le chien Max danse dans le jardin!Mon vélo rouge vole 42 fois en étéLuna a 7 ans!☀️
⚠️ Points importants
- Pas de trim() : Les espaces en début/fin sont préservés et font partie de l'identifiant
- Pas de vérification password == username : Sur demande du client, cette règle a été retirée
- Validation côté API : L'API vérifie les mots de passe contre la base Have I Been Pwned
- Rétrocompatibilité : Les anciens identifiants restent valides
🔄 Impact sur l'expérience utilisateur
| Aspect | Avant | Après NIST |
|---|---|---|
| Complexité | Règles strictes difficiles à mémoriser | Liberté totale, phrases naturelles |
| Longueur password | 12-16 caractères obligatoires | 8-64 caractères flexibles |
| Caractères spéciaux | Obligatoires | Optionnels |
| Mémorisation | Mots de passe complexes oubliés | Phrases personnelles mémorables |
| Sécurité | Règles arbitraires | Vérification contre bases de données compromises |
🎯 Gestion des rôles
Hiérarchie des permissions
Membre (Rôle 1) : Consultation et distribution dans ses secteurs Admin Amicale (Rôle 2) : Gestion complète de son amicale et ses membres Super Admin (Rôle 3+) : Administration globale multi-amicales
Fonctionnalités par rôle
Admin Amicale - Gestion des membres Création : Nouveaux membres avec attribution de rôle Modification : Informations personnelles, rôle, statut actif Suppression : Avec confirmation obligatoire Validation : Contrôle d'unicité email/username par l'API
Interface adaptative
Sélecteur de rôle : Visible uniquement pour les admins Checkbox statut actif : Contrôle d'accès aux comptes Édition contextuelle : Champs modifiables selon les permissions Actions conditionnelles : Boutons disponibles selon le niveau d'autorisation
👥 Gestion des membres (Admin Amicale)
🎯 Vue d'ensemble
La gestion des membres est une fonctionnalité clé pour les Admins Amicale (Rôle 2) qui permet une administration complète des équipes au sein de leur amicale. Cette interface centralise toutes les opérations liées aux membres avec une approche sécurisée et intuitive.
📱 Interface AdminAmicalePage
L'interface principale admin_amicale_page.dart offre une vue d'ensemble complète :
- Informations de l'amicale : Affichage des détails de l'amicale courante
- Liste des membres : Tableau interactif avec actions contextuelles
- Ajout de membres : Bouton d'action pour créer de nouveaux comptes
- Opération courante : Indication de l'opération active en cours
✨ Fonctionnalités principales
🆕 Création de nouveaux membres
// Workflow de création
1. Clic sur "Ajouter un membre"
2. Ouverture du UserFormDialog
3. Formulaire vierge avec valeurs par défaut
4. Sélection du rôle (Membre/Administrateur)
5. Configuration du statut actif
6. Validation et création via API
7. Attribution automatique à l'amicale courante
Champs obligatoires :
- Email (unique dans le système)
- Au moins un nom (nom de famille OU nom de tournée)
- Rôle dans l'amicale
Champs optionnels :
- Nom d'utilisateur (éditable pour les admins)
- Prénom, téléphones, dates
- Nom de tournée (pour identification terrain)
✏️ Modification des membres existants
// Actions disponibles
- Clic sur une ligne → Ouverture du formulaire d'édition
- Modification de tous les champs personnels
- Changement de rôle (Membre ↔ Administrateur)
- Activation/Désactivation du compte
- Gestion du nom de tournée
Workflow de modification :
- Sélection du membre dans le tableau
- Ouverture automatique du
UserFormDialog - Formulaire pré-rempli avec données existantes
- Modification des champs souhaités
- Validation et mise à jour via API
- Synchronisation automatique avec Hive
🗑️ Suppression intelligente des membres
La suppression des membres intègre une logique métier avancée pour préserver l'intégrité des données :
🔍 Vérification des passages
// Algorithme de vérification
1. Récupération de opération courante
2. Analyse des passages du membre pour cette opération
3. Classification : passages réalisés vs passages à finaliser
4. Comptage total des passages actifs
📊 Scénarios de suppression
Cas 1 : Aucun passage (suppression simple)
DELETE /users/{id}
- Confirmation simple
- Suppression directe
- Aucun transfert nécessaire
Cas 2 : Passages existants (suppression avec transfert)
DELETE /users/{id}?transfer_to={destinataire}&operation_id={operation}
- Dialog d'avertissement avec détails
- Sélection obligatoire d'un membre destinataire
- Transfert automatique de tous les passages
- Préservation de l'historique
Cas 3 : Alternative recommandée (désactivation)
// Mise à jour du statut
membre.copyWith(isActive: false)
- Préservation complète des données
- Blocage de la connexion
- Maintien de l'historique
- Réactivation possible ultérieurement
🔐 Sécurité et permissions
Contrôles d'accès
- Isolation par amicale : Admins limités à leur amicale
- Vérification des rôles : Validation côté client et serveur
- Opération courante : Filtrage par contexte d'opération
- Validation API : Contrôles d'unicité et cohérence
Gestion des erreurs
graph TD
A[Action utilisateur] --> B[Validation locale]
B --> C[Appel API]
C --> D{Succès ?}
D -->|Oui| E[Mise à jour Hive]
D -->|Non| F[Affichage erreur]
E --> G[Notification succès]
F --> H[Dialog reste ouvert]
🎨 Interface utilisateur
Tableaux interactifs
MembreTableWidget - Composant principal :
- Colonnes : ID, Prénom, Nom, Email, Rôle, Statut
- Actions : Modification, Suppression
- Alternance de couleurs pour lisibilité
- Tri et navigation intuitifs
MembreRowWidget - Ligne individuelle :
- Clic pour édition rapide
- Boutons d'action contextuels
- Indicateurs visuels de statut
- Tooltip informatifs
Formulaires adaptatifs
UserFormDialog - Modale réutilisable :
- Layout responsive (>900px vs mobile)
- Validation en temps réel
- Gestion des erreurs inline
- Sauvegarde avec feedback
📡 Synchronisation et réactivité
Architecture ValueListenableBuilder
// Écoute automatique des changements
ValueListenableBuilder<Box<MembreModel>>(
valueListenable: membreRepository.getMembresBox().listenable(),
builder: (context, membresBox, child) {
// Interface mise à jour automatiquement
final membres = membresBox.values
.where((m) => m.fkEntite == currentUser.fkEntite)
.toList();
return MembreTableWidget(membres: membres);
},
)
Principe "API First"
- Validation API : Tentative sur serveur en priorité
- Succès → Sauvegarde locale + mise à jour interface
- Erreur → Affichage message + maintien état local
- Cohérence : Données locales toujours synchronisées
🔄 Workflow complet
sequenceDiagram
participant A as Admin
participant UI as Interface
participant R as Repository
participant API as Serveur
participant H as Hive
A->>UI: Action membre
UI->>R: Appel repository
R->>API: Requête HTTP
API-->>R: Réponse
alt Succès
R->>H: Sauvegarde locale
H-->>UI: Notification changement
UI-->>A: Interface mise à jour
else Erreur
R-->>UI: Exception
UI-->>A: Message d'erreur
end
🎯 Bonnes pratiques
Pour les administrateurs
- Vérification avant suppression : Toujours examiner les passages
- Préférer la désactivation : Éviter la perte de données
- Noms de tournée : Utiliser des identifiants terrain clairs
- Rôles appropriés : Limiter les administrateurs aux besoins
Gestion des erreurs courantes
| Erreur | Cause | Solution |
|---|---|---|
| Email déjà utilisé | Duplication | Choisir un autre email |
| Membre avec passages | Données liées | Transférer ou désactiver |
| Aucune opération active | Configuration | Vérifier les opérations |
| Accès refusé | Permissions | Vérifier le rôle admin |
Cette architecture garantit une gestion des membres robuste, sécurisée et intuitive, optimisant l'efficacité administrative tout en préservant l'intégrité des données opérationnelles. 👥✨
🗺️ Cartes et géolocalisation
🎯 Architecture cartographique
Flutter Map : Rendu cartographique haute performance Providers de tuiles : Solution hybride Mapbox/OpenStreetMap Géolocalisation temps réel : Suivi GPS des équipes Secteurs géographiques : Visualisation et attribution dynamique Passages géolocalisés : Enregistrement précis des distributions
🌍 Configuration des tuiles de carte
GEOSECTOR v2.0 utilise une stratégie différenciée pour l'affichage des tuiles de carte selon la plateforme :
Configuration actuelle
| Plateforme | Provider | URL Template | Raison |
|---|---|---|---|
| Web | Mapbox | https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/ |
Token compatible avec l'API styles |
| Mobile | OpenStreetMap | https://tile.openstreetmap.org/{z}/{x}/{y}.png |
Gratuit, sans restriction de token |
Pourquoi cette approche ?
- Problème de permissions Mapbox : Les tokens Mapbox actuels (DEV/REC/PROD) n'ont pas les permissions pour l'API
styles/v1qui retourne une erreur 403 sur mobile - Solution pragmatique : OpenStreetMap fonctionne parfaitement sans token et offre une qualité de carte équivalente
- Facilité de maintenance : Pas besoin de gérer des tokens différents selon les environnements
💾 Système de cache des tuiles
Le cache fonctionne pour les deux providers (Mapbox et OpenStreetMap) :
Configuration du cache
// Dans mapbox_map.dart
CachedTileProvider(
store: FileCacheStore(cachePath),
maxStale: Duration(days: 30), // Tuiles valides 30 jours
)
Caches séparés par provider
- Web (Mapbox) : Cache dans
MapboxTileCache/ - Mobile (OSM) : Cache dans
OSMTileCache/
Avantages du cache
✅ Mode hors ligne : Les zones visitées restent disponibles sans connexion ✅ Performance : Chargement instantané des tuiles en cache ✅ Économie de données : Pas de re-téléchargement inutile ✅ Fiabilité : Fonctionne même avec une connexion instable
🔧 Widget MapboxMap centralisé
Le widget MapboxMap (lib/presentation/widgets/mapbox_map.dart) centralise toute la logique cartographique :
Paramètres principaux
MapboxMap(
initialPosition: LatLng(48.1173, -1.6778), // Rennes
initialZoom: 13.0,
markers: [...], // Marqueurs (passages, etc.)
polygons: [...], // Polygones (secteurs)
useOpenStreetMap: !kIsWeb, // OSM sur mobile, Mapbox sur web
showControls: true, // Boutons zoom/localisation
)
Utilisation dans l'application
- admin_map_page.dart : Carte d'administration avec édition de secteurs
- user_map_page.dart : Carte utilisateur avec visualisation des passages
- user_field_mode_page.dart : Mode terrain avec GPS et boussole
🔄 Migration future vers Mapbox complet
Si vous obtenez un token Mapbox avec les permissions appropriées :
- Retirer le paramètre
useOpenStreetMap: !kIsWebdans les pages - Le widget détectera automatiquement et utilisera Mapbox partout
- Le cache continuera de fonctionner avec le nouveau provider
📱 Mode terrain (Field Mode)
Le mode terrain offre des fonctionnalités avancées pour les équipes sur le terrain :
- Indicateurs GPS : Qualité du signal (GPS/Réseau) avec actualisation 5s
- Mode boussole : Orientation de la carte selon le magnétomètre
- Markers optimisés : Affichage simplifié (première lettre de rueBis)
- Liste triée : Passages organisés par distance
- Cercles de distance : Visualisation des zones de proximité
🔄 Synchronisation et réactivité
Hive + ValueListenableBuilder
Réactivité native : Mise à jour automatique de l'interface Performance optimisée : Pas de Provider, injection directe Écoute sélective : Réactivité fine par Box Hive Cohérence des données : Synchronisation bidirectionnelle User/Membre
Services Singleton
CurrentUserService : Gestion de l'utilisateur connecté CurrentAmicaleService : Amicale de l'utilisateur actuel ApiService : Communication centralisée avec l'API DataLoadingService : Orchestration du chargement des données
🚀 Optimisation des performances Hive
📈 Gestion des Box Hive avec cache
GEOSECTOR v2.0 implémente une stratégie de cache avancée pour les Box Hive afin d'éliminer les goulots d'étranglement de performance lors d'opérations haute fréquence.
🎯 Problème résolu
Avant l'optimisation, l'application effectuait jusqu'à 848 vérifications Hive.isBoxOpen() par chargement de page, causant des ralentissements significatifs lors du rendu des listes et du filtrage des données.
💡 Solution : Cache lazy des Box
// Pattern implémenté dans tous les repositories
class OptimizedRepository {
Box<ModelType>? _cachedBox;
Box<ModelType> get _modelBox {
if (_cachedBox == null) {
if (!Hive.isBoxOpen(AppKeys.boxName)) {
throw Exception('Box non ouverte');
}
_cachedBox = Hive.box<ModelType>(AppKeys.boxName);
debugPrint('Repository: Box mise en cache');
}
return _cachedBox!;
}
void _resetCache() {
_cachedBox = null;
}
}
🔄 Gestion du cache et réactivité
Point critique : Le cache doit être réinitialisé après toute modification pour garantir le bon fonctionnement de ValueListenableBuilder :
// ✅ OBLIGATOIRE après modification
Future<void> saveData(ModelType data) async {
await _modelBox.put(data.id, data);
_resetCache(); // ← Crucial pour la réactivité UI
notifyListeners();
}
// ✅ OBLIGATOIRE après suppression
Future<void> deleteData(int id) async {
await _modelBox.delete(id);
_resetCache(); // ← Crucial pour la réactivité UI
notifyListeners();
}
// ✅ OBLIGATOIRE après vidage ou traitement API
Future<void> processApiData(List<dynamic> data) async {
await _modelBox.clear();
// ... traitement des données
_resetCache(); // ← Crucial pour la réactivité UI
notifyListeners();
}
📊 Impact performance
| Métrique | Avant optimisation | Après optimisation |
|---|---|---|
| Vérifications box | 848 par page | 1 par session |
| Temps de rendu | 200-500ms | <50ms |
| Filtrage liste | Lent (check répétés) | Instantané |
| Mémoire | Overhead minimal | Impact négligeable |
🏗️ Repositories optimisés
L'optimisation est implémentée dans tous les repositories critiques :
- ✅ SectorRepository : Gestion des secteurs géographiques
- ✅ PassageRepository : Suivi des distributions
- ✅ MembreRepository : Gestion des équipes
- ✅ OperationRepository : Campagnes et opérations
- ✅ AmicaleRepository : Organisations
🎯 Règles d'implémentation
- Cache systématique : Tous les repositories fréquemment utilisés
- Reset obligatoire : Après toute opération de modification
- Getter lazy : Accès différé à la box uniquement si nécessaire
- Debug logging : Traçabilité du cache en développement
- Cohérence : Pattern appliqué uniformément
Cette architecture garantit une application performante, maintenable et évolutive avec une excellente expérience utilisateur. 🚀
🎨 Architecture des Dialogs Auto-Gérées
🎯 Principe de conception
GEOSECTOR v2.0 implémente une architecture simplifiée des dialogs qui élimine la complexité des callbacks asynchrones et garantit une gestion robuste des formulaires modaux.
🏗️ Pattern "Dialog Auto-Gérée"
graph TD
A[Page Parente] -->|Injection Repository| B[Dialog Auto-Gérée]
B -->|Appel Direct| C[Repository]
C -->|API Call| D[Serveur]
D -->|Réponse| C
C -->|Sauvegarde| E[Hive Local]
B -->|Auto-Fermeture| A
B -->|Callback Simple| A
E -->|ValueListenableBuilder| A
📱 Widgets de passages et navigation
🎯 PassagesListWidget
Le widget PassagesListWidget est le composant central pour l'affichage et la gestion des passages dans toute l'application. Il offre une expérience utilisateur cohérente avec des fonctionnalités adaptatives selon le contexte.
✨ Fonctionnalités principales
- Affichage adaptatif : Liste complète ou tableau de bord avec fond transparent
- Flux conditionnel de clic : Comportement intelligent selon le type de passage
- Bouton de création intégré : Bouton "+" vert dans l'en-tête pour ajouter des passages
- Filtrage avancé : Par type, utilisateur, période, avec exclusions possibles
- Actions contextuelles : Modification, suppression, génération de reçus
🔄 Flux conditionnel des clics sur passages
Le widget implémente un comportement intelligent lors du clic sur un passage :
// Logique de gestion des clics
void _handlePassageClick(Map<String, dynamic> passage) {
final int passageType = passage['type'] as int? ?? 1;
if (passageType == 2) {
// Type 2 (À finaliser) : Ouverture directe du formulaire d'édition
_showEditDialog(context, passageModel);
} else {
// Autres types : Affichage des détails avec option de modification
_showDetailsDialogWithEditOption(context, passage, passageModel);
}
}
Comportements par type de passage :
- Type 1 (Réalisé) : Affiche les détails complets avec option "Modifier"
- Type 2 (À finaliser) : Ouvre directement le formulaire d'édition pour finalisation rapide
- Type 3 (Absent) : Affiche les détails avec options limitées
- Type 4 (Refusé) : Affiche les détails en lecture seule
🎨 Dialog de détails amélioré
La boîte de dialogue des détails a été repensée pour une meilleure lisibilité :
- Organisation par sections : Client, Passage, Lieu avec icônes distinctives
- Badges colorés : Visualisation rapide du type et statut
- Formatage intelligent : Dates, montants et informations structurées
- Actions contextuelles : Boutons adaptés selon les permissions
➕ Bouton de création contextuel
Le widget intègre un bouton "+" vert flottant dans l'en-tête pour créer de nouveaux passages :
// Paramètres pour activer le bouton de création
PassagesListWidget(
showAddButton: true, // Active le bouton "+"
onAddPassage: () async {
// Logique de création avec PassageFormDialog
await _showPassageFormDialog(context);
},
)
Pages avec bouton de création activé :
user_field_mode_page.dart: Mode terrain pour création rapideuser_history_page.dart: Historique avec ajout possibleadmin_history_page.dart: Gestion administrative complète
🎯 DashboardAppBar - Évolution de l'interface
❌ Suppression du bouton "Nouveau passage"
Le bouton global "Nouveau passage" a été définitivement retiré de la barre d'application (DashboardAppBar) pour privilégier une approche contextuelle :
Avant :
- Bouton toujours visible dans l'AppBar
- Création de passage possible depuis n'importe quelle page
- Confusion possible sur le contexte de création
Après :
- Boutons "+" contextuels dans les pages appropriées
- Création limitée aux contextes pertinents
- Interface épurée et plus intuitive
🎨 Architecture simplifiée
La suppression du bouton global a permis de :
- Nettoyer les dépendances (
passage_form_dialog.dart,app_keys.dart) - Simplifier les paramètres de
DashboardLayout - Réduire la complexité de navigation
- Améliorer la cohérence UX
🎯 Mode tableau de bord
Pour les pages de tableau de bord, le PassagesListWidget s'adapte automatiquement :
🏠 Page d'accueil utilisateur
Dans user_dashboard_home_page.dart, l'affichage est optimisé :
// Configuration pour le tableau de bord
SizedBox(
height: 450, // Hauteur fixe pour éviter l'overflow
child: PassagesListWidget(
passages: recentPassages,
showFilters: false, // Pas de filtres sur le dashboard
showSearch: false, // Pas de recherche
maxPassages: 20, // Limite aux 20 plus récents
transparentBackground: true, // Fond transparent pour intégration
),
)
Améliorations du dashboard :
- Suppression de la Card wrapper pour un design épuré
- Fond transparent pour intégration harmonieuse
- En-tête coloré maintenu pour la lisibilité
- Limite augmentée à 20 passages récents (au lieu de 10)
✨ Composants de l'architecture
1. Page Parente (ex: AdminOperationsPage)
// Ouverture simplifiée de dialog
void _showEditOperationDialog(OperationModel op) {
showDialog(
context: context,
builder: (dialogContext) => OperationFormDialog(
operation: op,
operationRepository: widget.operationRepository, // ← Injection directe
onSuccess: () {
if (mounted) setState(() {}); // ← Simple rafraîchissement
},
),
);
}
2. Dialog Auto-Gérée (ex: OperationFormDialog)
// Gestion complète dans la dialog
void _handleSubmit() async {
try {
// Appel direct du repository
final success = await widget.operationRepository.saveOperationFromModel(operationData);
if (success && mounted) {
// Délai pour synchronisation Hive
Future.delayed(const Duration(milliseconds: 200), () {
Navigator.of(context).pop(); // ← Auto-fermeture
widget.onSuccess?.call(); // ← Notification simple
ApiException.showSuccess(context, "Opération sauvegardée");
});
}
} catch (e) {
ApiException.showError(context, e); // ← Erreur sans fermeture
}
}
🎯 Avantages de cette approche
| Aspect | Avant (Complexe) | Après (Simplifié) |
|---|---|---|
| Callbacks | Asynchrones complexes | Simple onSuccess: () => setState() |
| Fermeture | Gérée par le parent | Auto-fermeture dans la dialog |
| Gestion d'erreurs | Dispersée | Centralisée dans la dialog |
| Synchronisation | Problématique | Délai de 200ms pour Hive |
| Maintenance | Code dispersé | Logique unifiée |
🔧 Responsabilités claires
Page Parente
- ✅ Ouverture des dialogs avec injection de dépendances
- ✅ Rafraîchissement de l'interface via
setState() - ✅ Gestion des tableaux et listes intégrés
Dialog Auto-Gérée
- ✅ Validation et soumission du formulaire
- ✅ Appel direct des repositories
- ✅ Auto-fermeture en cas de succès
- ✅ Gestion des erreurs sans fermeture
Repository
- ✅ Logique métier (création vs modification)
- ✅ Appels API appropriés
- ✅ Sauvegarde locale dans Hive
- ✅ Propagation des exceptions
🚀 Exemple d'implémentation
// 1. Page parente - Code minimal
void _showCreateDialog() {
showDialog(
context: context,
builder: (context) => OperationFormDialog(
title: 'Créer une opération',
operationRepository: widget.operationRepository,
onSuccess: () => setState(() {}),
),
);
}
// 2. Dialog - Auto-gestion complète
class OperationFormDialog extends StatefulWidget {
final OperationRepository operationRepository;
final VoidCallback? onSuccess;
// Gestion interne : validation, API, fermeture, erreurs
}
// 3. Repository - Logique métier pure
Future<bool> saveOperationFromModel(OperationModel operation) async {
if (operation.id == 0) {
return await createOperation(...); // POST
} else {
return await updateOperation(...); // PUT
}
}
✅ Résultats obtenus
- 🎯 Simplicité : Code plus lisible et maintenable
- 🔒 Robustesse : Gestion d'erreurs centralisée
- ⚡ Performance : Synchronisation optimisée avec Hive
- 🎨 UX : Fermeture automatique et messages appropriés
- 🔧 Maintenance : Architecture cohérente et prévisible
Cette approche "Dialog Auto-Gérée" constitue un pattern architectural clé de GEOSECTOR v2.0, garantissant une expérience utilisateur fluide et un code maintenable. 🎉
Fonction de création d'une opération
Fonction de création d'une opération
🔄 Traitement des données complexes
Lors de la création d'une opération via OperationRepository.createOperation(), l'API retourne en réponse 201 Created ou 200 OK un payload complexe contenant 4 groupes de données essentiels :
{
"operations": [...], // 3 dernières opérations dont la nouvelle active
"secteurs": [...], // Secteurs de la nouvelle opération active
"passages": [...], // Passages de la nouvelle opération active
"users_sectors": [...] // Associations user-secteurs de la nouvelle opération
}
📦 Groupes de données attendus
🎯 Groupe operations
Contient les 3 dernières opérations de l'amicale, incluant celle qui vient d'être créée et qui devient automatiquement active.
🗺️ Groupe secteurs
Contient tous les secteurs géographiques associés à la dernière opération créée et active.
📍 Groupe passages
Contient l'ensemble des passages générés pour cette dernière opération créée et active.
👥 Groupe users_sectors
Contient les associations utilisateur-secteurs définissant les attributions pour cette dernière opération créée et active.
⚙️ Traitement automatique des données
Ces 4 groupes sont traités de manière identique au processus de connexion utilisateur, en utilisant le DataLoadingService avec la logique suivante :
// Dans OperationRepository._processCreationResponse()
await _processOperationCreationData(responseData);
Future<void> _processOperationCreationData(Map<String, dynamic> data) async {
// 1. Vidage des Box Hive concernées (comme au login)
await _clearRelatedBoxes();
// 2. Traitement des 4 groupes via DataLoadingService
if (data.containsKey('operations')) {
await DataLoadingService.instance.processOperationsFromApi(data['operations']);
}
if (data.containsKey('secteurs')) {
await DataLoadingService.instance.processSectorsFromApi(data['secteurs']);
}
if (data.containsKey('passages')) {
await DataLoadingService.instance.processPassagesFromApi(data['passages']);
}
if (data.containsKey('users_sectors')) {
await DataLoadingService.instance.processUserSectorsFromApi(data['users_sectors']);
}
}
Future<void> _clearRelatedBoxes() async {
// Vidage des Box respectives avant rechargement
final operationsBox = HiveService.instance.getTypedBox<OperationModel>(AppKeys.operationsBoxName);
final sectorsBox = HiveService.instance.getTypedBox<SectorModel>(AppKeys.sectorsBoxName);
final passagesBox = HiveService.instance.getTypedBox<PassageModel>(AppKeys.passagesBoxName);
final userSectorsBox = HiveService.instance.getTypedBox<UserSectorModel>(AppKeys.userSectorBoxName);
await operationsBox.clear();
await sectorsBox.clear();
await passagesBox.clear();
await userSectorsBox.clear();
}
🔄 Flux de synchronisation
sequenceDiagram
participant UI as Interface
participant OR as OperationRepository
participant API as Serveur API
participant HS as HiveService
participant DLS as DataLoadingService
participant VLB as ValueListenableBuilder
UI->>OR: createOperation(newOperation)
OR->>API: POST /operations
API-->>OR: 201 Created + 4 groupes
Note over OR: Traitement des données complexes
OR->>HS: clear() sur 4 Box concernées
OR->>DLS: processOperationsFromApi()
OR->>DLS: processSectorsFromApi()
OR->>DLS: processPassagesFromApi()
OR->>DLS: processUserSectorsFromApi()
Note over DLS: Sauvegarde dans Hive
DLS->>HS: put() dans Box typées
HS-->>VLB: Notifications de changement
VLB-->>UI: Mise à jour automatique
✅ Avantages de cette approche
- Cohérence totale : Données locales parfaitement synchronisées avec le serveur
- Performance optimisée : Un seul appel API pour toutes les données nécessaires
- Réactivité immédiate : Interface mise à jour automatiquement via ValueListenableBuilder
- Logique centralisée : Réutilisation du DataLoadingService existant
- Gestion d'erreurs : Rollback automatique en cas d'échec du traitement
Cette architecture garantit une synchronisation robuste et performante lors de la création d'opérations, en maintenant la cohérence des données tout en optimisant l'expérience utilisateur. 🚀
📝 Changelog
v2.1 (Janvier 2025)
Interface utilisateur
- 🎨 Suppression des titres de pages pour maximiser l'espace utile
- Pages utilisateur : historique, statistiques, carte
- Pages admin : historique, statistiques
- Module de chat
- 📱 Chat responsive avec layout adaptatif
- Desktop : disposition horizontale rooms/messages
- Mobile : disposition verticale avec hauteur adaptative
- 🗺️ Carte optimisée
- Mode plein écran
- Filtres en pastilles colorées overlay (bas gauche)
- Design minimaliste sans labels
- 📏 Titres responsive sur dashboards
- Tailles adaptées aux petits écrans
- Suppression des éléments superflus (icône refresh)
Corrections de bugs
- ✅ Fix backdrop persistant après fermeture de PassageFormDialog
- ✅ Fix contexte Navigator pour dialogs (rootNavigator: false)
- ✅ Fix responsive des titres sur petits écrans
v2.0 (Décembre 2024)
- 🏗️ Architecture moderne sans Provider
- 💾 Optimisation cache Hive
- 🔐 Normes NIST pour les identifiants
- 📊 Système de logging intelligent
- 🎯 Pattern Dialog Auto-Gérée