perf: optimization des rebuilds (ValueNotifier pour calendrier/container_form, Selector pour pages de gestion et mon compte)

This commit is contained in:
ElPoyo
2026-05-26 13:48:50 +02:00
parent 6ee63ed29c
commit 93c102012b
6 changed files with 101 additions and 92 deletions
+11 -7
View File
@@ -51,7 +51,7 @@ class _CalendarPageState extends State<CalendarPage> {
int _searchRequestId = 0; int _searchRequestId = 0;
bool _isMobileSearchVisible = false; bool _isMobileSearchVisible = false;
bool _isRefreshing = false; bool _isRefreshing = false;
double _detailsPaneFraction = 0.35; final ValueNotifier<double> _detailsPaneFraction = ValueNotifier<double>(0.35);
String? _lastLoadedUserId; String? _lastLoadedUserId;
bool _initialLoadScheduled = false; bool _initialLoadScheduled = false;
@@ -207,6 +207,7 @@ class _CalendarPageState extends State<CalendarPage> {
void dispose() { void dispose() {
_searchDebounce?.cancel(); _searchDebounce?.cancel();
_searchController.dispose(); _searchController.dispose();
_detailsPaneFraction.dispose();
super.dispose(); super.dispose();
} }
@@ -817,12 +818,10 @@ class _CalendarPageState extends State<CalendarPage> {
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onHorizontalDragUpdate: (details) { onHorizontalDragUpdate: (details) {
setState(() { _detailsPaneFraction.value = _clampDetailsPaneFraction(
_detailsPaneFraction = _clampDetailsPaneFraction( _detailsPaneFraction.value - (details.delta.dx / totalWidth),
_detailsPaneFraction - (details.delta.dx / totalWidth),
totalWidth, totalWidth,
); );
});
}, },
child: SizedBox( child: SizedBox(
width: _desktopResizeHandleWidth, width: _desktopResizeHandleWidth,
@@ -935,8 +934,11 @@ class _CalendarPageState extends State<CalendarPage> {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final totalWidth = constraints.maxWidth; final totalWidth = constraints.maxWidth;
return ValueListenableBuilder<double>(
valueListenable: _detailsPaneFraction,
builder: (context, fraction, child) {
final detailsPaneFraction = final detailsPaneFraction =
_clampDetailsPaneFraction(_detailsPaneFraction, totalWidth); _clampDetailsPaneFraction(fraction, totalWidth);
final detailsWidth = totalWidth * detailsPaneFraction; final detailsWidth = totalWidth * detailsPaneFraction;
final calendarWidth = final calendarWidth =
totalWidth - _desktopResizeHandleWidth - detailsWidth; totalWidth - _desktopResizeHandleWidth - detailsWidth;
@@ -957,6 +959,8 @@ class _CalendarPageState extends State<CalendarPage> {
); );
}, },
); );
},
);
} }
Widget _buildMobileLayout(List<EventModel> filteredEvents) { Widget _buildMobileLayout(List<EventModel> filteredEvents) {
@@ -1194,7 +1198,7 @@ class _CalendarPageState extends State<CalendarPage> {
), ),
), ),
if (!hasEvents) if (!hasEvents)
Center( const Center(
child: Text( child: Text(
'Aucun événement ne démarre à cette date'), 'Aucun événement ne démarre à cette date'),
), ),
+17 -13
View File
@@ -35,7 +35,7 @@ class _ContainerFormPageState extends State<ContainerFormPage> {
// Form fields // Form fields
ContainerType _selectedType = ContainerType.flightCase; ContainerType _selectedType = ContainerType.flightCase;
EquipmentStatus _selectedStatus = EquipmentStatus.available; EquipmentStatus _selectedStatus = EquipmentStatus.available;
bool _autoGenerateId = true; final ValueNotifier<bool> _autoGenerateIdNotifier = ValueNotifier<bool>(true);
final Set<String> _selectedEquipmentIds = {}; final Set<String> _selectedEquipmentIds = {};
bool _isEditing = false; bool _isEditing = false;
@@ -61,11 +61,11 @@ class _ContainerFormPageState extends State<ContainerFormPage> {
_heightController.text = container.height?.toString() ?? ''; _heightController.text = container.height?.toString() ?? '';
_notesController.text = container.notes ?? ''; _notesController.text = container.notes ?? '';
_selectedEquipmentIds.addAll(container.equipmentIds); _selectedEquipmentIds.addAll(container.equipmentIds);
_autoGenerateId = false; _autoGenerateIdNotifier.value = false;
} }
void _updateIdFromName() { void _updateIdFromName() {
if (_autoGenerateId && !_isEditing) { if (_autoGenerateIdNotifier.value && !_isEditing) {
final name = _nameController.text; final name = _nameController.text;
if (name.isNotEmpty) { if (name.isNotEmpty) {
final baseId = IdGenerator.generateContainerId( final baseId = IdGenerator.generateContainerId(
@@ -78,7 +78,7 @@ class _ContainerFormPageState extends State<ContainerFormPage> {
} }
void _updateIdFromType() { void _updateIdFromType() {
if (_autoGenerateId && !_isEditing) { if (_autoGenerateIdNotifier.value && !_isEditing) {
final name = _nameController.text; final name = _nameController.text;
if (name.isNotEmpty) { if (name.isNotEmpty) {
final baseId = IdGenerator.generateContainerId( final baseId = IdGenerator.generateContainerId(
@@ -123,7 +123,10 @@ class _ContainerFormPageState extends State<ContainerFormPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// ID // ID
Row( ValueListenableBuilder<bool>(
valueListenable: _autoGenerateIdNotifier,
builder: (context, autoGenerateId, child) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
@@ -135,7 +138,7 @@ class _ContainerFormPageState extends State<ContainerFormPage> {
border: OutlineInputBorder(), border: OutlineInputBorder(),
prefixIcon: Icon(Icons.qr_code), prefixIcon: Icon(Icons.qr_code),
), ),
enabled: !_autoGenerateId || _isEditing, enabled: !autoGenerateId || _isEditing,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Veuillez entrer un identifiant'; return 'Veuillez entrer un identifiant';
@@ -149,23 +152,23 @@ class _ContainerFormPageState extends State<ContainerFormPage> {
const SizedBox(width: 8), const SizedBox(width: 8),
IconButton( IconButton(
icon: Icon( icon: Icon(
_autoGenerateId ? Icons.lock : Icons.lock_open, autoGenerateId ? Icons.lock : Icons.lock_open,
color: _autoGenerateId ? AppColors.rouge : Colors.grey, color: autoGenerateId ? AppColors.rouge : Colors.grey,
), ),
tooltip: _autoGenerateId tooltip: autoGenerateId
? 'Génération automatique' ? 'Génération automatique'
: 'Saisie manuelle', : 'Saisie manuelle',
onPressed: () { onPressed: () {
setState(() { _autoGenerateIdNotifier.value = !autoGenerateId;
_autoGenerateId = !_autoGenerateId; if (_autoGenerateIdNotifier.value) {
if (_autoGenerateId) {
_updateIdFromName(); _updateIdFromName();
} }
});
}, },
), ),
], ],
], ],
);
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -632,6 +635,7 @@ class _ContainerFormPageState extends State<ContainerFormPage> {
_widthController.dispose(); _widthController.dispose();
_heightController.dispose(); _heightController.dispose();
_notesController.dispose(); _notesController.dispose();
_autoGenerateIdNotifier.dispose();
super.dispose(); super.dispose();
} }
} }
@@ -143,7 +143,7 @@ class _EquipmentManagementPageState extends State<EquipmentManagementPage>
], ],
], ],
) )
: CustomAppBar( : const CustomAppBar(
title: 'Gestion du matériel', title: 'Gestion du matériel',
), ),
drawer: const MainDrawer(currentPage: '/equipment_management'), drawer: const MainDrawer(currentPage: '/equipment_management'),
@@ -75,14 +75,15 @@ class _MaintenanceManagementPageState extends State<MaintenanceManagementPage> {
title: 'Gestion des maintenances', title: 'Gestion des maintenances',
), ),
drawer: const MainDrawer(currentPage: '/maintenance_management'), drawer: const MainDrawer(currentPage: '/maintenance_management'),
body: Consumer<MaintenanceProvider>( body: Selector<MaintenanceProvider, ({bool isLoading, List<MaintenanceModel> maintenances})>(
builder: (context, maintenanceProvider, _) { selector: (context, provider) => (isLoading: provider.isLoading, maintenances: provider.maintenances),
if (maintenanceProvider.isLoading) { builder: (context, data, _) {
if (data.isLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
final filteredMaintenances = _getFilteredMaintenances( final filteredMaintenances = _getFilteredMaintenances(
maintenanceProvider.maintenances, data.maintenances,
); );
return Column( return Column(
@@ -91,7 +92,7 @@ class _MaintenanceManagementPageState extends State<MaintenanceManagementPage> {
_buildFilterChips(), _buildFilterChips(),
// Statistiques // Statistiques
_buildStatsCards(maintenanceProvider), _buildStatsCards(data.maintenances),
// Liste des maintenances // Liste des maintenances
Expanded( Expanded(
@@ -148,10 +149,10 @@ class _MaintenanceManagementPageState extends State<MaintenanceManagementPage> {
); );
} }
Widget _buildStatsCards(MaintenanceProvider provider) { Widget _buildStatsCards(List<MaintenanceModel> maintenances) {
final upcoming = provider.maintenances.where((m) => !m.isCompleted && !m.isOverdue).length; final upcoming = maintenances.where((m) => !m.isCompleted && !m.isOverdue).length;
final overdue = provider.maintenances.where((m) => m.isOverdue).length; final overdue = maintenances.where((m) => m.isOverdue).length;
final completed = provider.maintenances.where((m) => m.isCompleted).length; final completed = maintenances.where((m) => m.isCompleted).length;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
+4 -5
View File
@@ -17,10 +17,9 @@ class MyAccountPage extends StatelessWidget {
title: 'Mon compte', title: 'Mon compte',
), ),
drawer: const MainDrawer(currentPage: '/my_account'), drawer: const MainDrawer(currentPage: '/my_account'),
body: Consumer<LocalUserProvider>( body: Selector<LocalUserProvider, UserModel?>(
builder: (context, userProvider, child) { selector: (context, provider) => provider.currentUser,
final user = userProvider.currentUser; builder: (context, user, child) {
if (user == null) { if (user == null) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@@ -73,7 +72,7 @@ class MyAccountPage extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
userProvider.updateUserData( context.read<LocalUserProvider>().updateUserData(
firstName: firstNameController.text, firstName: firstNameController.text,
lastName: lastNameController.text, lastName: lastNameController.text,
phoneNumber: phoneController.text, phoneNumber: phoneController.text,
+6 -5
View File
@@ -51,12 +51,13 @@ class _UserManagementPageState extends State<UserManagementPage> {
title: 'Gestion des utilisateurs', title: 'Gestion des utilisateurs',
), ),
drawer: const MainDrawer(currentPage: '/account_management'), drawer: const MainDrawer(currentPage: '/account_management'),
body: Consumer<UsersProvider>( body: Selector<UsersProvider, ({bool isLoading, List<UserModel> users})>(
builder: (context, usersProvider, child) { selector: (context, provider) => (isLoading: provider.isLoading, users: provider.users),
if (usersProvider.isLoading) { builder: (context, data, child) {
if (data.isLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
final users = usersProvider.users; final users = data.users;
if (users.isEmpty) { if (users.isEmpty) {
return const Center(child: Text("Aucun utilisateur trouvé")); return const Center(child: Text("Aucun utilisateur trouvé"));
} }
@@ -92,7 +93,7 @@ class _UserManagementPageState extends State<UserManagementPage> {
context: context, context: context,
builder: (_) => EditUserDialog(user: user)), builder: (_) => EditUserDialog(user: user)),
onResetPassword: () => _resetPassword(context, user), onResetPassword: () => _resetPassword(context, user),
onDelete: () => _confirmDeleteUser(context, usersProvider, user), onDelete: () => _confirmDeleteUser(context, context.read<UsersProvider>(), user),
); );
}, },
), ),