525 lines
16 KiB
Markdown
Executable File
525 lines
16 KiB
Markdown
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
|
|
```dart
|
|
// Dans UserRepository.login()
|
|
await ChatManager.instance.initializeChat();
|
|
```
|
|
|
|
#### Arrêt au logout
|
|
```dart
|
|
// Dans UserRepository.logout()
|
|
ChatManager.instance.dispose();
|
|
```
|
|
|
|
#### Gestion complète du cycle de vie de l'application
|
|
```dart
|
|
// 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)
|
|
```dart
|
|
@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
|
|
```dart
|
|
@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)
|
|
```http
|
|
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)
|
|
```http
|
|
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)
|
|
```http
|
|
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
|
|
```http
|
|
POST /api/chat/rooms
|
|
{
|
|
"title": "Nom du destinataire",
|
|
"type": "private",
|
|
"participants": [user_id],
|
|
"initial_message": "Message optionnel"
|
|
}
|
|
```
|
|
|
|
#### Envoi de message
|
|
```http
|
|
POST /api/chat/rooms/{room_id}/messages
|
|
{
|
|
"content": "Contenu du message"
|
|
}
|
|
```
|
|
|
|
#### Récupération des destinataires possibles
|
|
```http
|
|
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 `ConnectivityService` de l'app principale
|
|
- Pas de sync si déconnecté
|
|
- Affichage depuis cache local si hors ligne
|
|
|
|
#### Persistance
|
|
- `last_sync_timestamp` sauvegardé dans Hive
|
|
- Survit aux redémarrages d'application
|
|
- Reprise automatique des syncs incrémentales
|
|
|
|
### 6. Widgets principaux
|
|
|
|
#### ChatModule
|
|
```dart
|
|
// 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 :
|
|
```dart
|
|
// 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 :
|
|
```dart
|
|
// Dans HiveService._createSingleBox()
|
|
case 'Room':
|
|
await Hive.openBox<Room>(config.name);
|
|
break;
|
|
case 'Message':
|
|
await Hive.openBox<Message>(config.name);
|
|
break;
|
|
```
|
|
|
|
#### Utilisation dans ChatService
|
|
```dart
|
|
// 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 avec `ChangeNotifier` qui maintient le compte global des messages non lus
|
|
- **`BadgedIcon`** : Widget avec `AnimatedBuilder` qui écoute `ChatInfoService` et affiche automatiquement le badge
|
|
- **`createBadgedNavigationDestination`** : 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 `NavigationBar` inférieure
|
|
- **Desktop** : Badge affiché dans la sidebar (version normale et minimisée)
|
|
- **ResponsiveNavigation** : Préserve les `BadgedIcon` sans 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
|
|
- [x] Système de badges pour messages non lus
|
|
- [x] 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 |