import 'dart:convert'; import 'dart:async'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/dashboard_stats_model.dart'; import '../../config/dashboard_config.dart'; /// Gestionnaire de cache avancé pour le Dashboard class DashboardCacheManager { static const String _keyPrefix = 'dashboard_cache_'; static const String _keyDashboardData = '${_keyPrefix}data'; static const String _keyDashboardStats = '${_keyPrefix}stats'; static const String _keyRecentActivities = '${_keyPrefix}activities'; static const String _keyUpcomingEvents = '${_keyPrefix}events'; static const String _keyLastUpdate = '${_keyPrefix}last_update'; static const String _keyUserPreferences = '${_keyPrefix}user_prefs'; SharedPreferences? _prefs; final Map _memoryCache = {}; final Map _cacheTimestamps = {}; Timer? _cleanupTimer; /// Initialise le gestionnaire de cache Future initialize() async { _prefs = await SharedPreferences.getInstance(); _startCleanupTimer(); await _loadMemoryCache(); } /// Démarre le timer de nettoyage automatique void _startCleanupTimer() { _cleanupTimer = Timer.periodic( const Duration(minutes: 30), (_) => _cleanupExpiredCache(), ); } /// Charge le cache en mémoire au démarrage Future _loadMemoryCache() async { if (_prefs == null) return; final keys = _prefs!.getKeys().where((key) => key.startsWith(_keyPrefix)); for (final key in keys) { final value = _prefs!.getString(key); if (value != null) { try { final data = jsonDecode(value); _memoryCache[key] = data; // Charger le timestamp si disponible final timestampKey = '${key}_timestamp'; final timestamp = _prefs!.getInt(timestampKey); if (timestamp != null) { _cacheTimestamps[key] = DateTime.fromMillisecondsSinceEpoch(timestamp); } } catch (e) { // Supprimer les données corrompues await _prefs!.remove(key); } } } } /// Sauvegarde les données complètes du dashboard Future cacheDashboardData( DashboardDataModel data, String organizationId, String userId, ) async { final key = '${_keyDashboardData}_${organizationId}_$userId'; await _cacheData(key, data.toJson()); } /// Récupère les données complètes du dashboard Future getCachedDashboardData( String organizationId, String userId, ) async { final key = '${_keyDashboardData}_${organizationId}_$userId'; final data = await _getCachedData(key); if (data != null) { try { return DashboardDataModel.fromJson(data); } catch (e) { // Supprimer les données corrompues await _removeCachedData(key); return null; } } return null; } /// Sauvegarde les statistiques du dashboard Future cacheDashboardStats( DashboardStatsModel stats, String organizationId, String userId, ) async { final key = '${_keyDashboardStats}_${organizationId}_$userId'; await _cacheData(key, stats.toJson()); } /// Récupère les statistiques du dashboard Future getCachedDashboardStats( String organizationId, String userId, ) async { final key = '${_keyDashboardStats}_${organizationId}_$userId'; final data = await _getCachedData(key); if (data != null) { try { return DashboardStatsModel.fromJson(data); } catch (e) { await _removeCachedData(key); return null; } } return null; } /// Sauvegarde les activités récentes Future cacheRecentActivities( List activities, String organizationId, String userId, ) async { final key = '${_keyRecentActivities}_${organizationId}_$userId'; final data = activities.map((activity) => activity.toJson()).toList(); await _cacheData(key, data); } /// Récupère les activités récentes Future?> getCachedRecentActivities( String organizationId, String userId, ) async { final key = '${_keyRecentActivities}_${organizationId}_$userId'; final data = await _getCachedData(key); if (data != null && data is List) { try { return data .map((item) => RecentActivityModel.fromJson(item)) .toList(); } catch (e) { await _removeCachedData(key); return null; } } return null; } /// Sauvegarde les événements à venir Future cacheUpcomingEvents( List events, String organizationId, String userId, ) async { final key = '${_keyUpcomingEvents}_${organizationId}_$userId'; final data = events.map((event) => event.toJson()).toList(); await _cacheData(key, data); } /// Récupère les événements à venir Future?> getCachedUpcomingEvents( String organizationId, String userId, ) async { final key = '${_keyUpcomingEvents}_${organizationId}_$userId'; final data = await _getCachedData(key); if (data != null && data is List) { try { return data .map((item) => UpcomingEventModel.fromJson(item)) .toList(); } catch (e) { await _removeCachedData(key); return null; } } return null; } /// Sauvegarde les préférences utilisateur Future cacheUserPreferences( Map preferences, String userId, ) async { final key = '${_keyUserPreferences}_$userId'; await _cacheData(key, preferences); } /// Récupère les préférences utilisateur Future?> getCachedUserPreferences(String userId) async { final key = '${_keyUserPreferences}_$userId'; final data = await _getCachedData(key); if (data != null && data is Map) { return data; } return null; } /// Méthode générique pour sauvegarder des données Future _cacheData(String key, dynamic data) async { if (_prefs == null) return; try { final jsonString = jsonEncode(data); await _prefs!.setString(key, jsonString); // Sauvegarder le timestamp final timestamp = DateTime.now().millisecondsSinceEpoch; await _prefs!.setInt('${key}_timestamp', timestamp); // Mettre à jour le cache mémoire _memoryCache[key] = data; _cacheTimestamps[key] = DateTime.now(); } catch (e) { // Erreur de sérialisation, ignorer } } /// Méthode générique pour récupérer des données Future _getCachedData(String key) async { // Vérifier d'abord le cache mémoire if (_memoryCache.containsKey(key)) { if (_isCacheValid(key)) { return _memoryCache[key]; } else { // Cache expiré, le supprimer await _removeCachedData(key); return null; } } // Vérifier le cache persistant if (_prefs == null) return null; final jsonString = _prefs!.getString(key); if (jsonString != null) { try { final data = jsonDecode(jsonString); // Vérifier la validité du cache if (_isCacheValid(key)) { // Charger en mémoire pour les prochains accès _memoryCache[key] = data; return data; } else { // Cache expiré, le supprimer await _removeCachedData(key); return null; } } catch (e) { // Données corrompues, les supprimer await _removeCachedData(key); return null; } } return null; } /// Vérifie si le cache est encore valide bool _isCacheValid(String key) { final timestamp = _cacheTimestamps[key]; if (timestamp == null) { // Essayer de récupérer le timestamp depuis SharedPreferences final timestampMs = _prefs?.getInt('${key}_timestamp'); if (timestampMs != null) { final cacheTime = DateTime.fromMillisecondsSinceEpoch(timestampMs); _cacheTimestamps[key] = cacheTime; return DateTime.now().difference(cacheTime) < DashboardConfig.cacheExpiration; } return false; } return DateTime.now().difference(timestamp) < DashboardConfig.cacheExpiration; } /// Supprime des données du cache Future _removeCachedData(String key) async { _memoryCache.remove(key); _cacheTimestamps.remove(key); if (_prefs != null) { await _prefs!.remove(key); await _prefs!.remove('${key}_timestamp'); } } /// Nettoie le cache expiré Future _cleanupExpiredCache() async { final keysToRemove = []; for (final key in _cacheTimestamps.keys) { if (!_isCacheValid(key)) { keysToRemove.add(key); } } for (final key in keysToRemove) { await _removeCachedData(key); } } /// Vide tout le cache Future clearCache() async { _memoryCache.clear(); _cacheTimestamps.clear(); if (_prefs != null) { final keys = _prefs!.getKeys().where((key) => key.startsWith(_keyPrefix)); for (final key in keys) { await _prefs!.remove(key); } } } /// Vide le cache pour un utilisateur spécifique Future clearUserCache(String organizationId, String userId) async { final userKeys = [ '${_keyDashboardData}_${organizationId}_$userId', '${_keyDashboardStats}_${organizationId}_$userId', '${_keyRecentActivities}_${organizationId}_$userId', '${_keyUpcomingEvents}_${organizationId}_$userId', '${_keyUserPreferences}_$userId', ]; for (final key in userKeys) { await _removeCachedData(key); } } /// Obtient les statistiques du cache Map getCacheStats() { final totalKeys = _memoryCache.length; final validKeys = _cacheTimestamps.keys.where(_isCacheValid).length; final expiredKeys = totalKeys - validKeys; return { 'totalKeys': totalKeys, 'validKeys': validKeys, 'expiredKeys': expiredKeys, 'memoryUsage': _calculateMemoryUsage(), 'oldestEntry': _getOldestEntryAge(), 'newestEntry': _getNewestEntryAge(), }; } /// Calcule l'utilisation mémoire approximative int _calculateMemoryUsage() { int totalSize = 0; for (final data in _memoryCache.values) { try { totalSize += jsonEncode(data).length; } catch (e) { // Ignorer les erreurs de sérialisation } } return totalSize; } /// Obtient l'âge de l'entrée la plus ancienne Duration? _getOldestEntryAge() { if (_cacheTimestamps.isEmpty) return null; final oldestTimestamp = _cacheTimestamps.values .reduce((a, b) => a.isBefore(b) ? a : b); return DateTime.now().difference(oldestTimestamp); } /// Obtient l'âge de l'entrée la plus récente Duration? _getNewestEntryAge() { if (_cacheTimestamps.isEmpty) return null; final newestTimestamp = _cacheTimestamps.values .reduce((a, b) => a.isAfter(b) ? a : b); return DateTime.now().difference(newestTimestamp); } /// Libère les ressources void dispose() { _cleanupTimer?.cancel(); _memoryCache.clear(); _cacheTimestamps.clear(); } }