Files
geo/app/docs/chat.md

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 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

// 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 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

  • 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