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