import 'dart:convert'; import 'package:injectable/injectable.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/cotisation_model.dart'; import '../models/cotisation_statistics_model.dart'; import '../models/payment_model.dart'; /// Service de gestion du cache local /// Permet de stocker et récupérer des données en mode hors-ligne @LazySingleton() class CacheService { static const String _cotisationsCacheKey = 'cotisations_cache'; static const String _cotisationsStatsCacheKey = 'cotisations_stats_cache'; static const String _paymentsCacheKey = 'payments_cache'; static const String _lastSyncKey = 'last_sync_timestamp'; static const Duration _cacheValidityDuration = Duration(minutes: 30); final SharedPreferences _prefs; CacheService(this._prefs); /// Sauvegarde une liste de cotisations dans le cache Future saveCotisations(List cotisations, {String? key}) async { final cacheKey = key ?? _cotisationsCacheKey; final jsonList = cotisations.map((c) => c.toJson()).toList(); final jsonString = jsonEncode({ 'data': jsonList, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await _prefs.setString(cacheKey, jsonString); } /// Récupère une liste de cotisations depuis le cache Future?> getCotisations({String? key}) async { final cacheKey = key ?? _cotisationsCacheKey; final jsonString = _prefs.getString(cacheKey); if (jsonString == null) return null; try { final jsonData = jsonDecode(jsonString) as Map; final timestamp = DateTime.fromMillisecondsSinceEpoch(jsonData['timestamp'] as int); // Vérifier si le cache est encore valide if (DateTime.now().difference(timestamp) > _cacheValidityDuration) { await clearCotisations(key: key); return null; } final jsonList = jsonData['data'] as List; return jsonList.map((json) => CotisationModel.fromJson(json as Map)).toList(); } catch (e) { // En cas d'erreur, nettoyer le cache corrompu await clearCotisations(key: key); return null; } } /// Sauvegarde les statistiques des cotisations Future saveCotisationsStats(CotisationStatisticsModel stats) async { final jsonString = jsonEncode({ 'data': stats.toJson(), 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await _prefs.setString(_cotisationsStatsCacheKey, jsonString); } /// Récupère les statistiques des cotisations depuis le cache Future getCotisationsStats() async { final jsonString = _prefs.getString(_cotisationsStatsCacheKey); if (jsonString == null) return null; try { final jsonData = jsonDecode(jsonString) as Map; final timestamp = DateTime.fromMillisecondsSinceEpoch(jsonData['timestamp'] as int); // Vérifier si le cache est encore valide if (DateTime.now().difference(timestamp) > _cacheValidityDuration) { await clearCotisationsStats(); return null; } return CotisationStatisticsModel.fromJson(jsonData['data'] as Map); } catch (e) { await clearCotisationsStats(); return null; } } /// Sauvegarde une liste de paiements dans le cache Future savePayments(List payments) async { final jsonList = payments.map((p) => p.toJson()).toList(); final jsonString = jsonEncode({ 'data': jsonList, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await _prefs.setString(_paymentsCacheKey, jsonString); } /// Récupère une liste de paiements depuis le cache Future?> getPayments() async { final jsonString = _prefs.getString(_paymentsCacheKey); if (jsonString == null) return null; try { final jsonData = jsonDecode(jsonString) as Map; final timestamp = DateTime.fromMillisecondsSinceEpoch(jsonData['timestamp'] as int); // Vérifier si le cache est encore valide if (DateTime.now().difference(timestamp) > _cacheValidityDuration) { await clearPayments(); return null; } final jsonList = jsonData['data'] as List; return jsonList.map((json) => PaymentModel.fromJson(json as Map)).toList(); } catch (e) { await clearPayments(); return null; } } /// Sauvegarde une cotisation individuelle dans le cache Future saveCotisation(CotisationModel cotisation) async { final key = 'cotisation_${cotisation.id}'; final jsonString = jsonEncode({ 'data': cotisation.toJson(), 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await _prefs.setString(key, jsonString); } /// Récupère une cotisation individuelle depuis le cache Future getCotisation(String id) async { final key = 'cotisation_$id'; final jsonString = _prefs.getString(key); if (jsonString == null) return null; try { final jsonData = jsonDecode(jsonString) as Map; final timestamp = DateTime.fromMillisecondsSinceEpoch(jsonData['timestamp'] as int); // Vérifier si le cache est encore valide if (DateTime.now().difference(timestamp) > _cacheValidityDuration) { await clearCotisation(id); return null; } return CotisationModel.fromJson(jsonData['data'] as Map); } catch (e) { await clearCotisation(id); return null; } } /// Met à jour le timestamp de la dernière synchronisation Future updateLastSyncTimestamp() async { await _prefs.setInt(_lastSyncKey, DateTime.now().millisecondsSinceEpoch); } /// Récupère le timestamp de la dernière synchronisation DateTime? getLastSyncTimestamp() { final timestamp = _prefs.getInt(_lastSyncKey); return timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp) : null; } /// Vérifie si une synchronisation est nécessaire bool needsSync() { final lastSync = getLastSyncTimestamp(); if (lastSync == null) return true; return DateTime.now().difference(lastSync) > const Duration(minutes: 15); } /// Nettoie le cache des cotisations Future clearCotisations({String? key}) async { final cacheKey = key ?? _cotisationsCacheKey; await _prefs.remove(cacheKey); } /// Nettoie le cache des statistiques Future clearCotisationsStats() async { await _prefs.remove(_cotisationsStatsCacheKey); } /// Nettoie le cache des paiements Future clearPayments() async { await _prefs.remove(_paymentsCacheKey); } /// Nettoie une cotisation individuelle du cache Future clearCotisation(String id) async { final key = 'cotisation_$id'; await _prefs.remove(key); } /// Nettoie tout le cache des cotisations Future clearAllCotisationsCache() async { final keys = _prefs.getKeys().where((key) => key.startsWith('cotisation') || key == _cotisationsStatsCacheKey || key == _paymentsCacheKey ).toList(); for (final key in keys) { await _prefs.remove(key); } } /// Retourne la taille du cache en octets (approximation) int getCacheSize() { int totalSize = 0; final keys = _prefs.getKeys().where((key) => key.startsWith('cotisation') || key == _cotisationsStatsCacheKey || key == _paymentsCacheKey ); for (final key in keys) { final value = _prefs.getString(key); if (value != null) { totalSize += value.length * 2; // Approximation UTF-16 } } return totalSize; } /// Retourne des informations sur le cache Map getCacheInfo() { final lastSync = getLastSyncTimestamp(); return { 'lastSync': lastSync?.toIso8601String(), 'needsSync': needsSync(), 'cacheSize': getCacheSize(), 'cacheSizeFormatted': _formatBytes(getCacheSize()), }; } /// Formate la taille en octets en format lisible String _formatBytes(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; } }