250 lines
8.2 KiB
Dart
250 lines
8.2 KiB
Dart
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<void> saveCotisations(List<CotisationModel> 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<List<CotisationModel>?> 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<String, dynamic>;
|
|
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<dynamic>;
|
|
return jsonList.map((json) => CotisationModel.fromJson(json as Map<String, dynamic>)).toList();
|
|
} catch (e) {
|
|
// En cas d'erreur, nettoyer le cache corrompu
|
|
await clearCotisations(key: key);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Sauvegarde les statistiques des cotisations
|
|
Future<void> 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<CotisationStatisticsModel?> getCotisationsStats() async {
|
|
final jsonString = _prefs.getString(_cotisationsStatsCacheKey);
|
|
|
|
if (jsonString == null) return null;
|
|
|
|
try {
|
|
final jsonData = jsonDecode(jsonString) as Map<String, dynamic>;
|
|
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<String, dynamic>);
|
|
} catch (e) {
|
|
await clearCotisationsStats();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Sauvegarde une liste de paiements dans le cache
|
|
Future<void> savePayments(List<PaymentModel> 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<List<PaymentModel>?> getPayments() async {
|
|
final jsonString = _prefs.getString(_paymentsCacheKey);
|
|
|
|
if (jsonString == null) return null;
|
|
|
|
try {
|
|
final jsonData = jsonDecode(jsonString) as Map<String, dynamic>;
|
|
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<dynamic>;
|
|
return jsonList.map((json) => PaymentModel.fromJson(json as Map<String, dynamic>)).toList();
|
|
} catch (e) {
|
|
await clearPayments();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Sauvegarde une cotisation individuelle dans le cache
|
|
Future<void> 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<CotisationModel?> 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<String, dynamic>;
|
|
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<String, dynamic>);
|
|
} catch (e) {
|
|
await clearCotisation(id);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Met à jour le timestamp de la dernière synchronisation
|
|
Future<void> 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<void> clearCotisations({String? key}) async {
|
|
final cacheKey = key ?? _cotisationsCacheKey;
|
|
await _prefs.remove(cacheKey);
|
|
}
|
|
|
|
/// Nettoie le cache des statistiques
|
|
Future<void> clearCotisationsStats() async {
|
|
await _prefs.remove(_cotisationsStatsCacheKey);
|
|
}
|
|
|
|
/// Nettoie le cache des paiements
|
|
Future<void> clearPayments() async {
|
|
await _prefs.remove(_paymentsCacheKey);
|
|
}
|
|
|
|
/// Nettoie une cotisation individuelle du cache
|
|
Future<void> clearCotisation(String id) async {
|
|
final key = 'cotisation_$id';
|
|
await _prefs.remove(key);
|
|
}
|
|
|
|
/// Nettoie tout le cache des cotisations
|
|
Future<void> 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<String, dynamic> 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';
|
|
}
|
|
}
|