import 'dart:async'; import 'package:flutter/material.dart'; import '../../domain/entities/notification.dart' as domain; import '../datasources/notification_remote_data_source.dart'; import '../services/realtime_notification_service.dart'; import '../services/secure_storage.dart'; /// Service centralisé de gestion des notifications. /// /// Ce service gère: /// - Le compteur de notifications non lues /// - Le chargement des notifications depuis l'API /// - Les notifications in-app /// - Les listeners pour les changements /// /// **Usage avec Provider:** /// ```dart /// // Dans main.dart /// ChangeNotifierProvider( /// create: (_) => NotificationService( /// NotificationRemoteDataSource(http.Client()), /// SecureStorage(), /// )..initialize(), /// ), /// /// // Dans un widget /// final notificationService = Provider.of(context); /// final unreadCount = notificationService.unreadCount; /// ``` class NotificationService extends ChangeNotifier { NotificationService(this._dataSource, this._secureStorage); final NotificationRemoteDataSource _dataSource; final SecureStorage _secureStorage; List _notifications = []; bool _isLoading = false; Timer? _refreshTimer; // Service de notifications temps réel RealtimeNotificationService? _realtimeService; StreamSubscription? _systemNotificationSubscription; /// Liste de toutes les notifications List get notifications => List.unmodifiable(_notifications); /// Nombre total de notifications int get totalCount => _notifications.length; /// Nombre de notifications non lues int get unreadCount => _notifications.where((n) => !n.isRead).length; /// Indique si les notifications sont en cours de chargement bool get isLoading => _isLoading; /// Initialise le service (à appeler au démarrage de l'app). Future initialize() async { await loadNotifications(); // Actualise les notifications toutes les 2 minutes _startPeriodicRefresh(); } /// Charge les notifications depuis l'API. Future loadNotifications() async { _isLoading = true; notifyListeners(); try { final userId = await _secureStorage.getUserId(); if (userId == null || userId.isEmpty) { _isLoading = false; notifyListeners(); return; } final notificationModels = await _dataSource.getNotifications(userId); _notifications = notificationModels.map((model) => model.toEntity()).toList(); // Trie par timestamp décroissant (les plus récentes en premier) _notifications.sort((a, b) => b.timestamp.compareTo(a.timestamp)); } catch (e) { debugPrint('[NotificationService] Erreur chargement: $e'); } finally { _isLoading = false; notifyListeners(); } } /// Marque une notification comme lue. Future markAsRead(String notificationId) async { try { await _dataSource.markAsRead(notificationId); // Mise à jour locale final index = _notifications.indexWhere((n) => n.id == notificationId); if (index != -1) { _notifications[index] = _notifications[index].copyWith(isRead: true); notifyListeners(); } } catch (e) { debugPrint('[NotificationService] Erreur marquage lu: $e'); rethrow; } } /// Marque toutes les notifications comme lues. Future markAllAsRead() async { try { final userId = await _secureStorage.getUserId(); if (userId == null) return; await _dataSource.markAllAsRead(userId); // Mise à jour locale _notifications = _notifications.map((n) => n.copyWith(isRead: true)).toList(); notifyListeners(); } catch (e) { debugPrint('[NotificationService] Erreur marquage tout lu: $e'); rethrow; } } /// Supprime une notification. Future deleteNotification(String notificationId) async { try { await _dataSource.deleteNotification(notificationId); // Mise à jour locale _notifications.removeWhere((n) => n.id == notificationId); notifyListeners(); } catch (e) { debugPrint('[NotificationService] Erreur suppression: $e'); rethrow; } } /// Ajoute une nouvelle notification (simulation ou depuis push notification). /// /// Utilisé pour afficher une notification in-app quand une nouvelle /// notification arrive via Firebase Cloud Messaging. void addNotification(domain.Notification notification) { _notifications.insert(0, notification); notifyListeners(); } /// Démarre l'actualisation périodique des notifications. void _startPeriodicRefresh() { _refreshTimer?.cancel(); _refreshTimer = Timer.periodic( const Duration(minutes: 2), (_) => loadNotifications(), ); } /// Arrête l'actualisation périodique. void stopPeriodicRefresh() { _refreshTimer?.cancel(); _refreshTimer = null; } /// Connecte le service de notifications temps réel. /// /// Cette méthode remplace le polling par des notifications push en temps réel. /// Le polling est automatiquement désactivé lorsque le service temps réel est connecté. /// /// [service] : Le service de notifications temps réel à connecter. void connectRealtime(RealtimeNotificationService service) { _realtimeService = service; // IMPORTANT : Arrêter le polling puisqu'on passe en temps réel stopPeriodicRefresh(); debugPrint('[NotificationService] Polling arrêté, passage en mode temps réel'); // Écouter les notifications système en temps réel _systemNotificationSubscription = service.systemNotificationStream.listen( _handleSystemNotification, onError: (error) { debugPrint('[NotificationService] Erreur dans le stream de notifications système: $error'); }, ); debugPrint('[NotificationService] Service de notifications temps réel connecté'); } /// Gère les notifications système reçues en temps réel. /// /// Cette méthode est appelée automatiquement lorsqu'une notification /// est reçue via WebSocket. void _handleSystemNotification(SystemNotification notification) { debugPrint('[NotificationService] Notification système reçue: ${notification.title}'); // Convertir en entité domain final domainNotification = domain.Notification( id: notification.notificationId, title: notification.title, message: notification.message, type: _parseNotificationType(notification.type), timestamp: notification.timestamp, isRead: false, eventId: null, userId: '', // Le userId sera récupéré du contexte metadata: null, ); // Ajouter à la liste locale (en tête de liste pour avoir les plus récentes en premier) addNotification(domainNotification); debugPrint('[NotificationService] Notification ajoutée à la liste locale: ${notification.title}'); } /// Parse le type de notification depuis une chaîne. domain.NotificationType _parseNotificationType(String type) { switch (type.toLowerCase()) { case 'event': return domain.NotificationType.event; case 'friend': return domain.NotificationType.friend; case 'reminder': return domain.NotificationType.reminder; default: return domain.NotificationType.other; } } /// Déconnecte le service de notifications temps réel. /// /// Le polling est automatiquement redémarré lorsque le service est déconnecté. void disconnectRealtime() { _systemNotificationSubscription?.cancel(); _systemNotificationSubscription = null; _realtimeService = null; // Redémarrer le polling si déconnecté du temps réel _startPeriodicRefresh(); debugPrint('[NotificationService] Service temps réel déconnecté, reprise du polling'); } @override void dispose() { disconnectRealtime(); stopPeriodicRefresh(); super.dispose(); } }