16 KiB
Executable File
Module de Chat pour Geosector
Vue d'ensemble
Module de chat intégré pour l'application Geosector permettant la communication entre membres, administrateurs d'amicale et super-administrateurs, avec synchronisation temps réel et support hors ligne.
Architecture actuelle
Gestion du cycle de vie avec ChatManager
Le module chat utilise un service singleton ChatManager pour gérer son cycle de vie :
Initialisation au login
// Dans UserRepository.login()
await ChatManager.instance.initializeChat();
Arrêt au logout
// Dans UserRepository.logout()
ChatManager.instance.dispose();
Gestion complète du cycle de vie de l'application
// Dans GeosectorApp avec WidgetsBindingObserver
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
// App au premier plan - reprendre les syncs
ChatManager.instance.resumeSyncs();
break;
case AppLifecycleState.paused:
// App en arrière-plan - pause pour économiser batterie
ChatManager.instance.pauseSyncs();
break;
case AppLifecycleState.inactive:
// État transitoire (appel entrant) - ne rien faire
break;
case AppLifecycleState.detached:
// App vraiment fermée (rare sur mobile) - nettoyer
ChatManager.instance.dispose();
break;
case AppLifecycleState.hidden:
// App masquée (Flutter 3.13+) - pause comme paused
ChatManager.instance.pauseSyncs();
break;
}
}
Points clés du ChatManager
- Singleton : Une seule instance pour toute l'application
- Initialisation unique : Méthode idempotente, peut être appelée plusieurs fois sans effet
- Syncs en arrière-plan : Timer de 15 secondes actif uniquement quand l'app est au premier plan
- Gestion intelligente : Vérifie la connexion utilisateur avant chaque utilisation
- Économie de batterie : Pause automatique des syncs quand l'app passe en arrière-plan
- Sync de rattrapage : Synchronisation immédiate au retour au premier plan
- Nettoyage automatique : Arrêt propre des timers et libération des ressources
Comportement par état d'application
| État | Action | Raison |
|---|---|---|
| resumed | resumeSyncs() |
App au premier plan, syncs actives + sync de rattrapage |
| paused | pauseSyncs() |
App en arrière-plan, économie de batterie |
| inactive | Rien | État transitoire (ex: appel entrant sur iOS) |
| detached | dispose() |
App vraiment fermée (très rare sur mobile) |
| hidden | pauseSyncs() |
App masquée (Flutter 3.13+), traité comme paused |
Note importante : Sur mobile, "fermer" l'app (swipe) la met généralement en paused, pas en detached. Le chat arrête donc ses syncs pour économiser la batterie mais reste initialisé pour un retour rapide.
1. Structure des modèles
Room (Conversation)
@HiveType(typeId: 50)
class Room {
String id; // UUID de la conversation
String title; // Nom du destinataire ou titre du groupe
String type; // 'private', 'group', 'broadcast'
DateTime createdAt; // Date de création
DateTime? updatedAt; // Date de dernière modification
String? lastMessage; // Dernier message (preview)
DateTime? lastMessageAt; // Date du dernier message
int unreadCount; // Nombre de messages non lus
List<Map<String, dynamic>>? recentMessages; // 5 derniers messages (cache)
}
Message
@HiveType(typeId: 51)
class Message {
String id; // UUID du message
String roomId; // ID de la conversation
String content; // Contenu du message
int senderId; // ID de l'expéditeur
String senderName; // Nom de l'expéditeur
String? senderFirstName; // Prénom de l'expéditeur
DateTime sentAt; // Date d'envoi
bool isMe; // Message de l'utilisateur actuel
bool isRead; // Message lu/non lu
int? readCount; // Nombre de personnes ayant lu
}
2. Endpoints API implémentés
Synchronisation initiale (au login)
GET /api/chat/rooms
Réponse:
{
"status": "success",
"sync_timestamp": "2025-08-25T11:45:00Z",
"rooms": [
{
"id": "c6a4eaef-8efe-49c6-b896-11b4ca1a8056",
"title": "VAISSAIRE",
"type": "private",
"created_at": "2025-08-25 11:43:32",
"updated_at": "2025-08-25 11:44:15",
"last_message": "Salut, ça va ?",
"last_message_at": "2025-08-25 11:44:15",
"unread_count": 10,
"participant_count": 2,
"participants": [...],
"recent_messages": [
{
"id": "msg-123",
"content": "Salut Pierre !",
"sender_id": 9999985,
"sender_name": "VAISSAIRE",
"sender_first_name": "Clément",
"sent_at": "2025-08-25 11:44:00",
"is_read": false,
"is_mine": false
}
]
}
]
}
Synchronisation incrémentale (toutes les 15 secondes)
GET /api/chat/rooms?updated_after=2025-08-25T11:45:00Z
Réponse si changements:
{
"status": "success",
"sync_timestamp": "2025-08-25T11:45:15Z",
"has_changes": true,
"rooms": [
// Seulement les rooms modifiées
]
}
Réponse si aucun changement:
{
"status": "success",
"sync_timestamp": "2025-08-25T11:45:15Z",
"has_changes": false,
"rooms": []
}
Chargement des messages (marque automatiquement comme lu)
GET /api/chat/rooms/{room_id}/messages?limit=50
Réponse:
{
"status": "success",
"messages": [...],
"has_more": true,
"marked_as_read": 10, // Messages marqués comme lus automatiquement
"unread_count": 0 // Messages non lus restants
}
Création de conversation
POST /api/chat/rooms
{
"title": "Nom du destinataire",
"type": "private",
"participants": [user_id],
"initial_message": "Message optionnel"
}
Envoi de message
POST /api/chat/rooms/{room_id}/messages
{
"content": "Contenu du message"
}
Récupération des destinataires possibles
GET /api/chat/recipients?search=nom
Réponse:
{
"status": "success",
"recipients": [
{
"id": 10016609,
"name": "ANDREZIEUX",
"first_name": "Boris",
"sect_name": "Tournée Boris",
"role": 1,
"entite_id": 5,
"entite_name": "AMICALE TEST DEV PIERRE"
}
]
}
3. Règles métier par rôle
Membre (role = 1)
- Peut contacter les membres de son amicale
- Peut contacter les administrateurs de son amicale
- Ne peut pas contacter les super-admins directement
Admin Amicale (role = 2)
- Peut contacter tous les membres de son amicale
- Peut contacter les super-admins (GEOSECTOR)
- Bouton "Toute l'Amicale" pour message groupé
- Bouton "GEOSECTOR" pour contacter les super-admins
Super Admin (role = 9)
- Peut contacter tous les administrateurs d'amicale
- Peut créer des groupes broadcast
- Accès à toutes les conversations
4. Flux de synchronisation optimisé
1. Login/Initialisation
└─> UserRepository.login()
└─> ChatManager.instance.initializeChat()
└─> ChatModule.init() avec infos utilisateur
└─> GET /api/chat/rooms (sync complète initiale)
└─> Stockage dans Hive
└─> Timer démarré (15 secondes)
2. Syncs en arrière-plan (pendant toute la session)
└─> Timer toutes les 15 secondes
└─> Vérification connectivité
└─> GET /api/chat/rooms?updated_after=...
└─> Si has_changes=false → skip
└─> Si has_changes=true → merge changements
3. Ouverture page communication
└─> Vérification ChatManager.isReady
└─> Affichage immédiat depuis Hive (pas de requête)
└─> ValueListenableBuilder pour UI réactive
4. Ouverture conversation spécifique
└─> GET /api/chat/rooms/{id}/messages
└─> Marque automatiquement comme lu côté API
└─> Met à jour unread_count localement
└─> Cache les messages dans Hive
5. Pull to refresh (action utilisateur)
└─> GET /api/chat/rooms (sync complète forcée)
└─> Remplacement total des données
6. Logout/Fermeture app
└─> ChatManager.instance.dispose()
└─> Arrêt du timer de sync
└─> Nettoyage des ressources
5. Gestion hors ligne
Stockage Hive
- Box chat_rooms : Conversations avec recent_messages
- Box chat_messages : Messages (max 100 par room)
- Box chat_metadata : Timestamp de dernière sync
Détection de connectivité
- Utilise
ConnectivityServicede l'app principale - Pas de sync si déconnecté
- Affichage depuis cache local si hors ligne
Persistance
last_sync_timestampsauvegardé dans Hive- Survit aux redémarrages d'application
- Reprise automatique des syncs incrémentales
6. Widgets principaux
ChatModule
// Initialisation au login via ChatManager
// Ne pas appeler directement - utiliser ChatManager
await ChatManager.instance.initializeChat();
// Vérification avant utilisation dans les pages
if (ChatManager.instance.isReady) {
return ChatModule.getRoomsPage();
}
RoomsPageEmbedded
- Liste des conversations
- Pas de requête à l'ouverture (données depuis Hive)
- Pull to refresh pour sync manuelle
- Badge de messages non lus
- Affichage des 5 derniers messages (recent_messages)
- Actualisation automatique via ValueListenableBuilder
- Affichage amélioré des conversations :
- Titre avec prénom + nom complet (ex: "Boris ANDREZIEUX")
- Avatar avec 2 initiales (ex: "BA" pour Boris ANDREZIEUX)
- Gestion intelligente des titres spéciaux ("GS" pour GEOSECTOR, "AA" pour Administrateurs Amicale)
ChatPage
- Affichage d'une conversation
- Chargement automatique + marquage comme lu
- Pagination des messages anciens
- Envoi de messages avec état temporaire
- Mise à jour temps réel des messages reçus
RecipientSelector
- Sélection des destinataires pour admin (role = 2)
- Bouton "GEOSECTOR" pour super-admins
- Bouton "Toute l'Amicale" (inclut l'expéditeur)
- Sélection individuelle (exclut super-admins)
- Affichage prénom + nom sur première ligne
- Affichage du sectName uniquement (pas de fallback entité)
- Avatar avec 2 initiales (prénom + nom)
7. Optimisations implémentées
Performance
- ✅ Sync incrémentale avec
updated_after - ✅ Skip si
has_changes=false - ✅ Cache des 5 derniers messages par room
- ✅ Max 100 messages en cache par room
- ✅ Pas de requête à l'ouverture des pages
Bande passante
- ✅ Sync complète uniquement au login
- ✅ Sync incrémentale toutes les 15 secondes
- ✅ Transmission uniquement des changements
- ✅ Compression des réponses API
UX
- ✅ Affichage immédiat depuis cache
- ✅ Marquage automatique comme lu
- ✅ Badge de non-lus en temps réel via
ChatInfoService - ✅ Pull to refresh disponible
- ✅ Support hors ligne complet
- ✅ Affichage prénom + nom dans les titres de conversations
- ✅ Avatars avec initiales intelligentes (2 lettres)
8. Configuration YAML
Le fichier assets/chat_config.yaml définit :
- Permissions par rôle
- Messages UI localisés
- Règles de communication entre rôles
- Couleurs et labels des rôles
9. Intégration Hive
TypeAdapters
Les adapters sont enregistrés dans HiveAdapters.registerAll() au démarrage :
// Chat adapters - TypeIds 50-51
if (!Hive.isAdapterRegistered(50)) {
Hive.registerAdapter(RoomAdapter());
}
if (!Hive.isAdapterRegistered(51)) {
Hive.registerAdapter(MessageAdapter());
}
Boxes gérées centralement
Les boxes sont ouvertes dans splash_page.dart avec toutes les autres :
// Dans HiveService._createSingleBox()
case 'Room':
await Hive.openBox<Room>(config.name);
break;
case 'Message':
await Hive.openBox<Message>(config.name);
break;
Utilisation dans ChatService
// Le ChatService utilise les boxes déjà ouvertes
_roomsBox = Hive.box<Room>(AppKeys.chatRoomsBoxName);
_messagesBox = Hive.box<Message>(AppKeys.chatMessagesBoxName);
// Pas d'ouverture de box - juste utilisation
TypeIds réservés :
- 50 : Room
- 51 : Message
10. Système de badges pour messages non lus
Architecture du système de badges
ChatInfoService: Service singleton avecChangeNotifierqui maintient le compte global des messages non lusBadgedIcon: Widget avecAnimatedBuilderqui écouteChatInfoServiceet affiche automatiquement le badgecreateBadgedNavigationDestination: Helper pour créer des destinations de navigation avec badge
Flux de mise à jour des badges
Sync toutes les 15 secondes
↓
ChatService calcule le total des unreadCount
↓
ChatInfoService.updateStats() appelé
↓
notifyListeners() déclenché
↓
BadgedIcon se redessine automatiquement
Intégration dans la navigation
- Mobile : Badge affiché dans la
NavigationBarinférieure - Desktop : Badge affiché dans la sidebar (version normale et minimisée)
- ResponsiveNavigation : Préserve les
BadgedIconsans les recréer
11. Points d'attention
Sécurité
- Token Bearer dans tous les appels API
- Vérification des permissions côté serveur
- Pas de données sensibles en cache local
- ChatManager vérifie l'authentification avant chaque utilisation
Maintenance
- Logs détaillés pour debug (à retirer en production)
- Sync incrémentale limite la charge réseau
- Gestion des erreurs avec fallback sur cache
- Timer de sync s'arrête automatiquement au logout/fermeture
Evolution
- Prêt pour WebSocket (remplacement du polling)
- Support des pièces jointes prévu
- Indicateurs de frappe possibles
- Architecture modulaire facilite les évolutions
Affichage des noms et avatars
Format d'affichage
- Conversations privées : Prénom + Nom (ex: "Boris ANDREZIEUX")
- Conversations de groupe : Liste des participants avec prénoms + noms
- Avatars : 2 initiales (prénom + nom) ou codes spéciaux pour groupes
Initiales intelligentes
| Type de conversation | Affichage | Avatar |
|---|---|---|
| Personne seule | "Boris ANDREZIEUX" | "BA" |
| GEOSECTOR Support | "GEOSECTOR Support" | "GS" |
| Administrateurs Amicale | "Administrateurs Amicale" | "AA" |
| Groupe Amicale | "Amicale - Tous les membres" | "AM" |
| Groupe multiple | "Boris A., Pierre D. et 3 autres" | Premières initiales |
Prochaines étapes
Court terme
- Système de badges pour messages non lus
- Affichage prénom + nom dans les conversations
- Retirer les logs de debug en production
- Ajouter la pagination infinie des messages
- Implémenter la recherche dans les conversations
Moyen terme
- Support des pièces jointes (images, fichiers)
- Indicateurs de frappe en temps réel
- Notifications push pour nouveaux messages
Long terme
- Migration vers WebSocket pour temps réel
- Support des messages vocaux
- Chiffrement end-to-end des messages
Migration depuis l'ancien système
Le nouveau système remplace complètement l'ancien avec :
- Meilleure gestion des permissions par rôle
- Synchronisation optimisée avec sync incrémentale
- Support hors ligne natif via Hive
- UI plus réactive avec ValueListenableBuilder
- ChatManager pour gestion centralisée du cycle de vie
- Initialisation automatique au login
- Syncs en arrière-plan pendant toute la session
Maintenance
Monitoring
- Vérifier les logs de sync toutes les 15 secondes
- Surveiller la taille des boxes Hive
- Analyser les erreurs de connectivité
Debug
Les logs actuels affichent :
- 🔄 Synchronisations (complètes/incrémentales)
- ✅ Rooms parsées et sauvegardées
- 📵 Détection perte de connexion
- ⏰ Timestamps de synchronisation
- 💾 Opérations Hive
Performance
- Sync incrémentale : < 100ms si pas de changements
- Sync complète : < 500ms pour 50 conversations
- Affichage initial : instantané (depuis cache)
- Envoi message : < 200ms en ligne