perf: ajout d'un Debouncer 400ms sur toutes les barres de recherche

This commit is contained in:
ElPoyo
2026-05-26 13:41:21 +02:00
parent 0bbc77ffc8
commit 32f1718a8c
4 changed files with 103 additions and 62 deletions
@@ -1,4 +1,4 @@
import 'package:em2rp/utils/debug_log.dart';
import 'package:em2rp/utils/debug_log.dart';
import 'package:flutter/material.dart';
import 'package:em2rp/models/equipment_model.dart';
import 'package:em2rp/models/container_model.dart';
@@ -7,6 +7,7 @@ import 'package:em2rp/services/event_availability_service.dart';
import 'package:em2rp/services/data_service.dart';
import 'package:em2rp/services/api_service.dart';
import 'package:em2rp/utils/colors.dart';
import 'package:em2rp/utils/debouncer.dart';
/// Type de s├®lection dans le dialog
enum SelectionType { equipment, container }
@@ -108,6 +109,7 @@ class _EquipmentSelectionDialogState extends State<EquipmentSelectionDialog> {
bool _isLoadingConflicts = false;
String _searchQuery = '';
final _searchDebouncer = Debouncer();
// Nouvelles options d'affichage
bool _showConflictingItems = false; // Afficher les ├®quipements/bo├«tes en conflit
@@ -435,6 +437,7 @@ class _EquipmentSelectionDialogState extends State<EquipmentSelectionDialog> {
_searchController.dispose();
_scrollController.dispose(); // Nettoyer le ScrollController
_selectionChangeNotifier.dispose(); // Nettoyer le ValueNotifier
_searchDebouncer.dispose();
super.dispose();
}
@@ -1209,7 +1212,7 @@ class _EquipmentSelectionDialogState extends State<EquipmentSelectionDialog> {
onChanged: (value) {
setState(() => _searchQuery = value.toLowerCase());
// Recharger depuis le d├®but avec le nouveau filtre
_reloadData();
_searchDebouncer(_reloadData);
},
),
@@ -1379,12 +1382,12 @@ class _EquipmentSelectionDialogState extends State<EquipmentSelectionDialog> {
// Containers
final filteredContainers = _paginatedContainers.where((container) {
if (!_showConflictingItems) {
// V├®rifier si le container lui-m├¬me est en conflit
// Vérifier si le container lui-même est en conflit
if (_conflictingContainerIds.contains(container.id)) {
return false;
}
// V├®rifier si le container a des ├®quipements enfants en conflit
// Vérifier si le container a des équipements enfants en conflit
final hasConflictingChildren = container.equipmentIds.any(
(eqId) => _conflictingEquipmentIds.contains(eqId),
);
@@ -1412,60 +1415,73 @@ class _EquipmentSelectionDialogState extends State<EquipmentSelectionDialog> {
}
return false;
},
child: ListView(
controller: _scrollController,
padding: const EdgeInsets.all(16),
children: [
// Header
_buildSectionHeader(
_displayType == SelectionType.equipment ? 'Équipements' : 'Containers',
_displayType == SelectionType.equipment ? Icons.inventory_2 : Icons.inventory,
itemWidgets.length,
),
const SizedBox(height: 12),
// Items
...itemWidgets,
// Indicateur de chargement en bas
if (_isLoadingMore)
const Padding(
padding: EdgeInsets.all(16),
child: Center(
child: CircularProgressIndicator(color: AppColors.rouge),
child: Builder(
builder: (context) {
// ✅ Construction de la liste complète avant le ListView.builder
// pour permettre l'utilisation de ListView.builder (lazy rendering)
// à la place de ListView(children:[]) qui construit tous les widgets
// en mémoire d'un coup.
// Pas d'itemExtent : les cards ont des hauteurs très variables
// (80px sans conflit, jusqu'à 300px+ avec détails de conflits).
final List<Widget> allChildren = [
// Header
_buildSectionHeader(
_displayType == SelectionType.equipment ? 'Équipements' : 'Containers',
_displayType == SelectionType.equipment ? Icons.inventory_2 : Icons.inventory,
itemWidgets.length,
),
),
const SizedBox(height: 12),
// Message si fin de liste
if (!_isLoadingMore && !(_displayType == SelectionType.equipment ? _hasMoreEquipments : _hasMoreContainers))
Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Text(
'Fin de la liste',
style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
// Items
...itemWidgets,
// Indicateur de chargement en bas
if (_isLoadingMore)
const Padding(
padding: EdgeInsets.all(16),
child: Center(
child: CircularProgressIndicator(color: AppColors.rouge),
),
),
),
),
// Message si rien trouv├®
if (itemWidgets.isEmpty && !_isLoadingMore)
Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
children: [
Icon(Icons.search_off, size: 64, color: Colors.grey.shade400),
const SizedBox(height: 16),
Text(
'Aucun r├®sultat trouv├®',
style: TextStyle(fontSize: 16, color: Colors.grey.shade600),
// Message si fin de liste
if (!_isLoadingMore && !(_displayType == SelectionType.equipment ? _hasMoreEquipments : _hasMoreContainers))
Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Text(
'Fin de la liste',
style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
),
],
),
),
),
),
],
// Message si rien trouvé
if (itemWidgets.isEmpty && !_isLoadingMore)
Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
children: [
Icon(Icons.search_off, size: 64, color: Colors.grey.shade400),
const SizedBox(height: 16),
Text(
'Aucun résultat trouvé',
style: TextStyle(fontSize: 16, color: Colors.grey.shade600),
),
],
),
),
),
];
return ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(16),
itemCount: allChildren.length,
itemBuilder: (context, index) => allChildren[index],
);
},
),
);
},