Refactoring

This commit is contained in:
DahoudG
2025-09-17 17:54:06 +00:00
parent 12d514d866
commit 63fe107f98
165 changed files with 54220 additions and 276 deletions

View File

@@ -0,0 +1,338 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// Service d'optimisation des performances pour l'application UnionFlow
///
/// Fournit des utilitaires pour :
/// - Optimisation des widgets
/// - Gestion de la mémoire
/// - Mise en cache intelligente
/// - Monitoring des performances
class PerformanceOptimizer {
static const String _tag = 'PerformanceOptimizer';
/// Singleton instance
static final PerformanceOptimizer _instance = PerformanceOptimizer._internal();
factory PerformanceOptimizer() => _instance;
PerformanceOptimizer._internal();
/// Cache pour les widgets optimisés
final Map<String, Widget> _widgetCache = {};
/// Cache pour les images
final Map<String, ImageProvider> _imageCache = {};
/// Compteurs de performance
final Map<String, int> _performanceCounters = {};
/// Temps de début pour les mesures
final Map<String, DateTime> _performanceTimers = {};
// ========================================
// OPTIMISATION DES WIDGETS
// ========================================
/// Optimise un widget avec RepaintBoundary si nécessaire
static Widget optimizeWidget(Widget child, {
String? key,
bool forceRepaintBoundary = false,
bool addSemantics = true,
}) {
Widget optimized = child;
// Ajouter RepaintBoundary pour les widgets complexes
if (forceRepaintBoundary || _shouldAddRepaintBoundary(child)) {
optimized = RepaintBoundary(
key: key != null ? Key('repaint_$key') : null,
child: optimized,
);
}
// Ajouter Semantics pour l'accessibilité
if (addSemantics && _shouldAddSemantics(child)) {
optimized = Semantics(
key: key != null ? Key('semantics_$key') : null,
child: optimized,
);
}
return optimized;
}
/// Détermine si un RepaintBoundary est nécessaire
static bool _shouldAddRepaintBoundary(Widget widget) {
// Ajouter RepaintBoundary pour les widgets qui changent fréquemment
return widget is AnimatedWidget ||
widget is CustomPaint ||
widget is Image ||
widget.runtimeType.toString().contains('Chart') ||
widget.runtimeType.toString().contains('Graph');
}
/// Détermine si Semantics est nécessaire
static bool _shouldAddSemantics(Widget widget) {
return widget is GestureDetector ||
widget is InkWell ||
widget is ElevatedButton ||
widget is TextButton ||
widget is IconButton;
}
/// Crée un widget avec mise en cache
Widget cachedWidget(String key, Widget Function() builder) {
if (_widgetCache.containsKey(key)) {
return _widgetCache[key]!;
}
final widget = builder();
_widgetCache[key] = widget;
return widget;
}
/// Nettoie le cache des widgets
void clearWidgetCache() {
_widgetCache.clear();
debugPrint('$_tag: Widget cache cleared');
}
// ========================================
// OPTIMISATION DES IMAGES
// ========================================
/// Optimise le chargement d'une image
static ImageProvider optimizeImage(String path, {
double? width,
double? height,
BoxFit fit = BoxFit.cover,
}) {
// Utiliser ResizeImage pour optimiser la mémoire
if (width != null || height != null) {
return ResizeImage(
AssetImage(path),
width: width?.round(),
height: height?.round(),
);
}
return AssetImage(path);
}
/// Met en cache une image
ImageProvider cachedImage(String key, String path) {
if (_imageCache.containsKey(key)) {
return _imageCache[key]!;
}
final image = AssetImage(path);
_imageCache[key] = image;
return image;
}
/// Précharge les images critiques
static Future<void> preloadCriticalImages(BuildContext context, List<String> imagePaths) async {
final futures = imagePaths.map((path) =>
precacheImage(AssetImage(path), context)
).toList();
await Future.wait(futures);
debugPrint('$_tag: ${imagePaths.length} critical images preloaded');
}
// ========================================
// MONITORING DES PERFORMANCES
// ========================================
/// Démarre un timer de performance
void startTimer(String operation) {
_performanceTimers[operation] = DateTime.now();
}
/// Arrête un timer et log le résultat
void stopTimer(String operation) {
final startTime = _performanceTimers[operation];
if (startTime != null) {
final duration = DateTime.now().difference(startTime);
debugPrint('$_tag: $operation took ${duration.inMilliseconds}ms');
_performanceTimers.remove(operation);
// Incrémenter le compteur
_performanceCounters[operation] = (_performanceCounters[operation] ?? 0) + 1;
}
}
/// Incrémente un compteur de performance
void incrementCounter(String metric) {
_performanceCounters[metric] = (_performanceCounters[metric] ?? 0) + 1;
}
/// Obtient les statistiques de performance
Map<String, int> getPerformanceStats() {
return Map.from(_performanceCounters);
}
/// Réinitialise les statistiques
void resetStats() {
_performanceCounters.clear();
_performanceTimers.clear();
debugPrint('$_tag: Performance stats reset');
}
// ========================================
// OPTIMISATION MÉMOIRE
// ========================================
/// Force le garbage collection (debug uniquement)
static void forceGarbageCollection() {
if (kDebugMode) {
// Forcer le GC en créant et supprimant des objets
final temp = List.generate(1000, (i) => Object());
temp.clear();
debugPrint('PerformanceOptimizer: Forced garbage collection');
}
}
/// Nettoie tous les caches
void clearAllCaches() {
clearWidgetCache();
_imageCache.clear();
debugPrint('$_tag: All caches cleared');
}
/// Obtient la taille des caches
Map<String, int> getCacheSizes() {
return {
'widgets': _widgetCache.length,
'images': _imageCache.length,
};
}
// ========================================
// OPTIMISATION DES ANIMATIONS
// ========================================
/// Crée un AnimationController optimisé
static AnimationController createOptimizedController({
required Duration duration,
required TickerProvider vsync,
double? value,
Duration? reverseDuration,
String? debugLabel,
}) {
return AnimationController(
duration: duration,
reverseDuration: reverseDuration,
vsync: vsync,
value: value,
debugLabel: debugLabel ?? 'OptimizedController',
);
}
/// Dispose proprement une liste d'AnimationControllers
static void disposeControllers(List<AnimationController> controllers) {
for (final controller in controllers) {
try {
controller.dispose();
} catch (e) {
// Controller déjà disposé, ignorer l'erreur
debugPrint('$_tag: Controller already disposed: $e');
}
}
controllers.clear();
}
// ========================================
// UTILITAIRES DE PERFORMANCE
// ========================================
/// Vérifie si l'appareil est performant
static bool isHighPerformanceDevice() {
// Logique basée sur les capacités de l'appareil
// Pour l'instant, retourne true par défaut
return true;
}
/// Obtient le niveau de performance recommandé
static PerformanceLevel getRecommendedPerformanceLevel() {
if (isHighPerformanceDevice()) {
return PerformanceLevel.high;
} else {
return PerformanceLevel.medium;
}
}
/// Applique les optimisations selon le niveau de performance
static void applyPerformanceLevel(PerformanceLevel level) {
switch (level) {
case PerformanceLevel.high:
// Toutes les animations et effets activés
debugPrint('$_tag: High performance mode enabled');
break;
case PerformanceLevel.medium:
// Animations réduites
debugPrint('$_tag: Medium performance mode enabled');
break;
case PerformanceLevel.low:
// Animations désactivées
debugPrint('$_tag: Low performance mode enabled');
break;
}
}
// ========================================
// MONITORING EN TEMPS RÉEL
// ========================================
/// Démarre le monitoring des performances
void startPerformanceMonitoring() {
// Monitoring du frame rate
WidgetsBinding.instance.addPersistentFrameCallback((timeStamp) {
_monitorFrameRate();
});
// Monitoring de la mémoire (toutes les 30 secondes)
Timer.periodic(const Duration(seconds: 30), (_) {
_monitorMemoryUsage();
});
debugPrint('$_tag: Performance monitoring started');
}
void _monitorFrameRate() {
// Logique de monitoring du frame rate
// Pour l'instant, juste incrémenter un compteur
incrementCounter('frames_rendered');
}
void _monitorMemoryUsage() {
// Logique de monitoring de la mémoire
if (kDebugMode) {
final cacheSize = getCacheSizes();
debugPrint('$_tag: Cache sizes - Widgets: ${cacheSize['widgets']}, Images: ${cacheSize['images']}');
}
}
}
/// Niveaux de performance
enum PerformanceLevel {
low,
medium,
high,
}
/// Extension pour optimiser les widgets
extension WidgetOptimization on Widget {
/// Optimise ce widget
Widget optimized({
String? key,
bool forceRepaintBoundary = false,
bool addSemantics = true,
}) {
return PerformanceOptimizer.optimizeWidget(
this,
key: key,
forceRepaintBoundary: forceRepaintBoundary,
addSemantics: addSemantics,
);
}
}

View File

@@ -0,0 +1,356 @@
import 'dart:convert';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:injectable/injectable.dart';
/// Service de mise en cache intelligent pour optimiser les performances
///
/// Fonctionnalités :
/// - Cache multi-niveaux (mémoire + stockage)
/// - Expiration automatique des données
/// - Invalidation intelligente
/// - Compression des données
/// - Statistiques de cache
@singleton
class SmartCacheService {
static const String _tag = 'SmartCacheService';
/// Cache en mémoire (niveau 1)
final Map<String, CacheEntry> _memoryCache = {};
/// Instance SharedPreferences pour le cache persistant
SharedPreferences? _prefs;
/// Statistiques du cache
final CacheStats _stats = CacheStats();
/// Taille maximale du cache mémoire (nombre d'entrées)
static const int _maxMemoryCacheSize = 100;
/// Durée par défaut de validité du cache
static const Duration _defaultCacheDuration = Duration(minutes: 15);
/// Initialise le service de cache
Future<void> initialize() async {
_prefs = await SharedPreferences.getInstance();
await _cleanExpiredEntries();
debugPrint('$_tag: Service initialized');
}
// ========================================
// OPÉRATIONS DE CACHE PRINCIPALES
// ========================================
/// Met en cache une valeur avec une clé
Future<void> put<T>(
String key,
T value, {
Duration? duration,
CacheLevel level = CacheLevel.both,
bool compress = false,
}) async {
final entry = CacheEntry(
key: key,
value: value,
timestamp: DateTime.now(),
duration: duration ?? _defaultCacheDuration,
compressed: compress,
);
// Cache mémoire
if (level == CacheLevel.memory || level == CacheLevel.both) {
_putInMemory(key, entry);
}
// Cache persistant
if (level == CacheLevel.storage || level == CacheLevel.both) {
await _putInStorage(key, entry);
}
_stats.incrementWrites();
debugPrint('$_tag: Cached $key (level: $level)');
}
/// Récupère une valeur du cache
Future<T?> get<T>(String key, {CacheLevel level = CacheLevel.both}) async {
CacheEntry? entry;
// Essayer d'abord le cache mémoire (plus rapide)
if (level == CacheLevel.memory || level == CacheLevel.both) {
entry = _getFromMemory(key);
if (entry != null && !entry.isExpired) {
_stats.incrementHits();
debugPrint('$_tag: Memory cache hit for $key');
return entry.value as T?;
}
}
// Essayer le cache persistant
if (level == CacheLevel.storage || level == CacheLevel.both) {
entry = await _getFromStorage(key);
if (entry != null && !entry.isExpired) {
// Remettre en cache mémoire pour les prochains accès
_putInMemory(key, entry);
_stats.incrementHits();
debugPrint('$_tag: Storage cache hit for $key');
return entry.value as T?;
}
}
_stats.incrementMisses();
debugPrint('$_tag: Cache miss for $key');
return null;
}
/// Vérifie si une clé existe dans le cache
Future<bool> contains(String key, {CacheLevel level = CacheLevel.both}) async {
if (level == CacheLevel.memory || level == CacheLevel.both) {
final entry = _getFromMemory(key);
if (entry != null && !entry.isExpired) return true;
}
if (level == CacheLevel.storage || level == CacheLevel.both) {
final entry = await _getFromStorage(key);
if (entry != null && !entry.isExpired) return true;
}
return false;
}
/// Supprime une entrée du cache
Future<void> remove(String key, {CacheLevel level = CacheLevel.both}) async {
if (level == CacheLevel.memory || level == CacheLevel.both) {
_memoryCache.remove(key);
}
if (level == CacheLevel.storage || level == CacheLevel.both) {
await _prefs?.remove(_getStorageKey(key));
}
debugPrint('$_tag: Removed $key from cache');
}
/// Vide complètement le cache
Future<void> clear({CacheLevel level = CacheLevel.both}) async {
if (level == CacheLevel.memory || level == CacheLevel.both) {
_memoryCache.clear();
}
if (level == CacheLevel.storage || level == CacheLevel.both) {
final keys = _prefs?.getKeys().where((k) => k.startsWith('cache_')).toList() ?? [];
for (final key in keys) {
await _prefs?.remove(key);
}
}
_stats.reset();
debugPrint('$_tag: Cache cleared (level: $level)');
}
// ========================================
// CACHE MÉMOIRE
// ========================================
void _putInMemory(String key, CacheEntry entry) {
// Vérifier la taille du cache et nettoyer si nécessaire
if (_memoryCache.length >= _maxMemoryCacheSize) {
_evictOldestMemoryEntry();
}
_memoryCache[key] = entry;
}
CacheEntry? _getFromMemory(String key) {
return _memoryCache[key];
}
void _evictOldestMemoryEntry() {
if (_memoryCache.isEmpty) return;
String? oldestKey;
DateTime? oldestTime;
for (final entry in _memoryCache.entries) {
if (oldestTime == null || entry.value.timestamp.isBefore(oldestTime)) {
oldestTime = entry.value.timestamp;
oldestKey = entry.key;
}
}
if (oldestKey != null) {
_memoryCache.remove(oldestKey);
debugPrint('$_tag: Evicted oldest memory entry: $oldestKey');
}
}
// ========================================
// CACHE PERSISTANT
// ========================================
Future<void> _putInStorage(String key, CacheEntry entry) async {
final storageKey = _getStorageKey(key);
final jsonData = entry.toJson();
await _prefs?.setString(storageKey, jsonEncode(jsonData));
}
Future<CacheEntry?> _getFromStorage(String key) async {
final storageKey = _getStorageKey(key);
final jsonString = _prefs?.getString(storageKey);
if (jsonString == null) return null;
try {
final jsonData = jsonDecode(jsonString) as Map<String, dynamic>;
return CacheEntry.fromJson(jsonData);
} catch (e) {
debugPrint('$_tag: Error deserializing cache entry $key: $e');
await _prefs?.remove(storageKey);
return null;
}
}
String _getStorageKey(String key) => 'cache_$key';
// ========================================
// NETTOYAGE ET MAINTENANCE
// ========================================
/// Nettoie les entrées expirées
Future<void> _cleanExpiredEntries() async {
// Nettoyer le cache mémoire
final expiredMemoryKeys = _memoryCache.entries
.where((entry) => entry.value.isExpired)
.map((entry) => entry.key)
.toList();
for (final key in expiredMemoryKeys) {
_memoryCache.remove(key);
}
// Nettoyer le cache persistant
final allKeys = _prefs?.getKeys().where((k) => k.startsWith('cache_')).toList() ?? [];
int cleanedCount = 0;
for (final storageKey in allKeys) {
final key = storageKey.substring(6); // Enlever 'cache_'
final entry = await _getFromStorage(key);
if (entry?.isExpired == true) {
await _prefs?.remove(storageKey);
cleanedCount++;
}
}
debugPrint('$_tag: Cleaned ${expiredMemoryKeys.length} memory entries and $cleanedCount storage entries');
}
/// Nettoie périodiquement le cache
void startPeriodicCleanup() {
Timer.periodic(const Duration(minutes: 30), (_) {
_cleanExpiredEntries();
});
}
// ========================================
// STATISTIQUES
// ========================================
/// Obtient les statistiques du cache
CacheStats getStats() => _stats;
/// Obtient des informations détaillées sur le cache
Future<CacheInfo> getCacheInfo() async {
final memorySize = _memoryCache.length;
final storageKeys = _prefs?.getKeys().where((k) => k.startsWith('cache_')).length ?? 0;
return CacheInfo(
memoryEntries: memorySize,
storageEntries: storageKeys,
stats: _stats,
);
}
}
/// Niveaux de cache
enum CacheLevel {
memory, // Cache en mémoire uniquement
storage, // Cache persistant uniquement
both, // Les deux niveaux
}
/// Entrée de cache
class CacheEntry {
final String key;
final dynamic value;
final DateTime timestamp;
final Duration duration;
final bool compressed;
CacheEntry({
required this.key,
required this.value,
required this.timestamp,
required this.duration,
this.compressed = false,
});
bool get isExpired => DateTime.now().difference(timestamp) > duration;
Map<String, dynamic> toJson() => {
'key': key,
'value': value,
'timestamp': timestamp.millisecondsSinceEpoch,
'duration': duration.inMilliseconds,
'compressed': compressed,
};
factory CacheEntry.fromJson(Map<String, dynamic> json) => CacheEntry(
key: json['key'],
value: json['value'],
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp']),
duration: Duration(milliseconds: json['duration']),
compressed: json['compressed'] ?? false,
);
}
/// Statistiques du cache
class CacheStats {
int _hits = 0;
int _misses = 0;
int _writes = 0;
int get hits => _hits;
int get misses => _misses;
int get writes => _writes;
double get hitRate => (_hits + _misses) > 0 ? _hits / (_hits + _misses) : 0.0;
void incrementHits() => _hits++;
void incrementMisses() => _misses++;
void incrementWrites() => _writes++;
void reset() {
_hits = 0;
_misses = 0;
_writes = 0;
}
@override
String toString() => 'CacheStats(hits: $_hits, misses: $_misses, writes: $_writes, hitRate: ${(hitRate * 100).toStringAsFixed(1)}%)';
}
/// Informations sur le cache
class CacheInfo {
final int memoryEntries;
final int storageEntries;
final CacheStats stats;
CacheInfo({
required this.memoryEntries,
required this.storageEntries,
required this.stats,
});
@override
String toString() => 'CacheInfo(memory: $memoryEntries, storage: $storageEntries, $stats)';
}