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,376 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import '../../../core/performance/performance_optimizer.dart';
/// ListView optimisé avec lazy loading intelligent et gestion de performance
///
/// Fonctionnalités :
/// - Lazy loading avec seuil configurable
/// - Recyclage automatique des widgets
/// - Animations optimisées
/// - Gestion mémoire intelligente
/// - Monitoring des performances
class OptimizedListView<T> extends StatefulWidget {
/// Liste des éléments à afficher
final List<T> items;
/// Builder pour chaque élément
final Widget Function(BuildContext context, T item, int index) itemBuilder;
/// Callback pour charger plus d'éléments
final Future<void> Function()? onLoadMore;
/// Callback pour rafraîchir la liste
final Future<void> Function()? onRefresh;
/// Indique si plus d'éléments peuvent être chargés
final bool hasMore;
/// Indique si le chargement est en cours
final bool isLoading;
/// Seuil pour déclencher le chargement (nombre d'éléments avant la fin)
final int loadMoreThreshold;
/// Hauteur estimée d'un élément (pour l'optimisation)
final double? itemExtent;
/// Padding de la liste
final EdgeInsetsGeometry? padding;
/// Séparateur entre les éléments
final Widget? separator;
/// Widget affiché quand la liste est vide
final Widget? emptyWidget;
/// Widget de chargement personnalisé
final Widget? loadingWidget;
/// Activer les animations
final bool enableAnimations;
/// Durée des animations
final Duration animationDuration;
/// Contrôleur de scroll personnalisé
final ScrollController? scrollController;
/// Physics du scroll
final ScrollPhysics? physics;
/// Activer le recyclage des widgets
final bool enableRecycling;
/// Nombre maximum de widgets en cache
final int maxCachedWidgets;
const OptimizedListView({
super.key,
required this.items,
required this.itemBuilder,
this.onLoadMore,
this.onRefresh,
this.hasMore = true,
this.isLoading = false,
this.loadMoreThreshold = 3,
this.itemExtent,
this.padding,
this.separator,
this.emptyWidget,
this.loadingWidget,
this.enableAnimations = true,
this.animationDuration = const Duration(milliseconds: 300),
this.scrollController,
this.physics,
this.enableRecycling = true,
this.maxCachedWidgets = 50,
});
@override
State<OptimizedListView<T>> createState() => _OptimizedListViewState<T>();
}
class _OptimizedListViewState<T> extends State<OptimizedListView<T>>
with TickerProviderStateMixin {
late ScrollController _scrollController;
late AnimationController _animationController;
/// Cache des widgets recyclés
final Map<String, Widget> _widgetCache = {};
/// Performance optimizer instance
final _optimizer = PerformanceOptimizer();
/// Indique si le chargement est en cours
bool _isLoadingMore = false;
@override
void initState() {
super.initState();
_scrollController = widget.scrollController ?? ScrollController();
_animationController = PerformanceOptimizer.createOptimizedController(
duration: widget.animationDuration,
vsync: this,
debugLabel: 'OptimizedListView',
);
// Écouter le scroll pour le lazy loading
_scrollController.addListener(_onScroll);
// Démarrer les animations si activées
if (widget.enableAnimations) {
_animationController.forward();
}
_optimizer.startTimer('list_build');
}
@override
void dispose() {
if (widget.scrollController == null) {
_scrollController.dispose();
}
_animationController.dispose();
_widgetCache.clear();
_optimizer.stopTimer('list_build');
super.dispose();
}
void _onScroll() {
if (!_scrollController.hasClients) return;
final position = _scrollController.position;
final maxScroll = position.maxScrollExtent;
final currentScroll = position.pixels;
// Calculer si on approche de la fin
final threshold = maxScroll - (widget.loadMoreThreshold * (widget.itemExtent ?? 100));
if (currentScroll >= threshold &&
widget.hasMore &&
!_isLoadingMore &&
widget.onLoadMore != null) {
_loadMore();
}
}
Future<void> _loadMore() async {
if (_isLoadingMore) return;
setState(() {
_isLoadingMore = true;
});
_optimizer.startTimer('load_more');
try {
await widget.onLoadMore!();
} finally {
if (mounted) {
setState(() {
_isLoadingMore = false;
});
}
_optimizer.stopTimer('load_more');
}
}
Widget _buildOptimizedItem(BuildContext context, int index) {
if (index >= widget.items.length) {
// Widget de chargement en fin de liste
return _buildLoadingIndicator();
}
final item = widget.items[index];
final cacheKey = 'item_${item.hashCode}_$index';
// Utiliser le cache si le recyclage est activé
if (widget.enableRecycling && _widgetCache.containsKey(cacheKey)) {
_optimizer.incrementCounter('cache_hit');
return _widgetCache[cacheKey]!;
}
// Construire le widget
Widget itemWidget = widget.itemBuilder(context, item, index);
// Optimiser le widget
itemWidget = PerformanceOptimizer.optimizeWidget(
itemWidget,
key: 'optimized_$index',
forceRepaintBoundary: true,
);
// Ajouter les animations si activées
if (widget.enableAnimations) {
itemWidget = _buildAnimatedItem(itemWidget, index);
}
// Mettre en cache si le recyclage est activé
if (widget.enableRecycling) {
_cacheWidget(cacheKey, itemWidget);
}
_optimizer.incrementCounter('item_built');
return itemWidget;
}
Widget _buildAnimatedItem(Widget child, int index) {
final delay = Duration(milliseconds: (index * 50).clamp(0, 500));
return AnimatedBuilder(
animation: _animationController,
builder: (context, _) {
final animationValue = Curves.easeOutCubic.transform(
(_animationController.value - (delay.inMilliseconds / widget.animationDuration.inMilliseconds))
.clamp(0.0, 1.0),
);
return Transform.translate(
offset: Offset(0, 50 * (1 - animationValue)),
child: Opacity(
opacity: animationValue,
child: child,
),
);
},
);
}
void _cacheWidget(String key, Widget widget) {
// Limiter la taille du cache
if (_widgetCache.length >= widget.maxCachedWidgets) {
// Supprimer les plus anciens (simple FIFO)
final oldestKey = _widgetCache.keys.first;
_widgetCache.remove(oldestKey);
}
_widgetCache[key] = widget;
}
Widget _buildLoadingIndicator() {
return widget.loadingWidget ??
const Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: CircularProgressIndicator(),
),
);
}
Widget _buildEmptyState() {
return widget.emptyWidget ??
const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.inbox_outlined,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'Aucun élément à afficher',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
// Liste vide
if (widget.items.isEmpty && !widget.isLoading) {
return _buildEmptyState();
}
// Calculer le nombre total d'éléments (items + indicateur de chargement)
final itemCount = widget.items.length + (widget.hasMore && _isLoadingMore ? 1 : 0);
Widget listView;
if (widget.separator != null) {
// ListView avec séparateurs
listView = ListView.separated(
controller: _scrollController,
physics: widget.physics,
padding: widget.padding,
itemCount: itemCount,
itemBuilder: _buildOptimizedItem,
separatorBuilder: (context, index) => widget.separator!,
);
} else {
// ListView standard
listView = ListView.builder(
controller: _scrollController,
physics: widget.physics,
padding: widget.padding,
itemCount: itemCount,
itemExtent: widget.itemExtent,
itemBuilder: _buildOptimizedItem,
);
}
// Ajouter RefreshIndicator si onRefresh est fourni
if (widget.onRefresh != null) {
listView = RefreshIndicator(
onRefresh: widget.onRefresh!,
child: listView,
);
}
return listView;
}
}
/// Extension pour faciliter l'utilisation
extension OptimizedListViewExtension<T> on List<T> {
/// Crée un OptimizedListView à partir de cette liste
Widget toOptimizedListView({
required Widget Function(BuildContext context, T item, int index) itemBuilder,
Future<void> Function()? onLoadMore,
Future<void> Function()? onRefresh,
bool hasMore = false,
bool isLoading = false,
int loadMoreThreshold = 3,
double? itemExtent,
EdgeInsetsGeometry? padding,
Widget? separator,
Widget? emptyWidget,
Widget? loadingWidget,
bool enableAnimations = true,
Duration animationDuration = const Duration(milliseconds: 300),
ScrollController? scrollController,
ScrollPhysics? physics,
bool enableRecycling = true,
int maxCachedWidgets = 50,
}) {
return OptimizedListView<T>(
items: this,
itemBuilder: itemBuilder,
onLoadMore: onLoadMore,
onRefresh: onRefresh,
hasMore: hasMore,
isLoading: isLoading,
loadMoreThreshold: loadMoreThreshold,
itemExtent: itemExtent,
padding: padding,
separator: separator,
emptyWidget: emptyWidget,
loadingWidget: loadingWidget,
enableAnimations: enableAnimations,
animationDuration: animationDuration,
scrollController: scrollController,
physics: physics,
enableRecycling: enableRecycling,
maxCachedWidgets: maxCachedWidgets,
);
}
}