Files
unionflow-client-quarkus-pr…/unionflow-mobile-apps/lib/features/dashboard/data/cache/dashboard_cache_manager.dart
2025-11-17 16:02:04 +00:00

401 lines
11 KiB
Dart

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<String, dynamic> _memoryCache = {};
final Map<String, DateTime> _cacheTimestamps = {};
Timer? _cleanupTimer;
/// Initialise le gestionnaire de cache
Future<void> 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<void> _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<void> 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<DashboardDataModel?> 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<void> 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<DashboardStatsModel?> 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<void> cacheRecentActivities(
List<RecentActivityModel> 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<List<RecentActivityModel>?> 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<void> cacheUpcomingEvents(
List<UpcomingEventModel> 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<List<UpcomingEventModel>?> 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<void> cacheUserPreferences(
Map<String, dynamic> preferences,
String userId,
) async {
final key = '${_keyUserPreferences}_$userId';
await _cacheData(key, preferences);
}
/// Récupère les préférences utilisateur
Future<Map<String, dynamic>?> getCachedUserPreferences(String userId) async {
final key = '${_keyUserPreferences}_$userId';
final data = await _getCachedData(key);
if (data != null && data is Map<String, dynamic>) {
return data;
}
return null;
}
/// Méthode générique pour sauvegarder des données
Future<void> _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<dynamic> _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<void> _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<void> _cleanupExpiredCache() async {
final keysToRemove = <String>[];
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<void> 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<void> 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<String, dynamic> 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();
}
}