bugfix: resolve runtime issues - encoding accents, ListView dynamic heights, notifyListeners exceptions during build phase, and layout overflows

This commit is contained in:
ElPoyo
2026-05-26 14:48:39 +02:00
parent 9bc4e88e46
commit f8f6cfb102
6 changed files with 399 additions and 397 deletions
+3 -3
View File
@@ -80,7 +80,7 @@ class EquipmentProvider extends ChangeNotifier {
Future<void> loadEquipments() async { Future<void> loadEquipments() async {
print('[EquipmentProvider] Starting to load ALL equipments...'); print('[EquipmentProvider] Starting to load ALL equipments...');
_isLoading = true; _isLoading = true;
notifyListeners(); scheduleMicrotask(notifyListeners);
try { try {
_equipment.clear(); _equipment.clear();
@@ -272,7 +272,7 @@ class EquipmentProvider extends ChangeNotifier {
_lastVisible = null; _lastVisible = null;
_hasMore = true; _hasMore = true;
_isLoading = true; _isLoading = true;
notifyListeners(); scheduleMicrotask(notifyListeners);
try { try {
await loadNextPage(); await loadNextPage();
@@ -296,7 +296,7 @@ class EquipmentProvider extends ChangeNotifier {
_isLoadingMore = true; _isLoadingMore = true;
_isLoading = true; _isLoading = true;
notifyListeners(); scheduleMicrotask(notifyListeners);
try { try {
final result = await _dataService.getEquipmentsPaginated( final result = await _dataService.getEquipmentsPaginated(
+1 -1
View File
@@ -28,7 +28,7 @@ class AppInitializer with ChangeNotifier {
Future<void> initialize() async { Future<void> initialize() async {
if (_isInitialized || _isInitializing) return; if (_isInitialized || _isInitializing) return;
_isInitializing = true; _isInitializing = true;
notifyListeners(); scheduleMicrotask(() => notifyListeners());
try { try {
// Initialiser Firebase // Initialiser Firebase
+228 -222
View File
@@ -980,237 +980,243 @@ class _CalendarPageState extends State<CalendarPage> {
? eventsForSelectedDay[_selectedEventIndex] ? eventsForSelectedDay[_selectedEventIndex]
: null; : null;
// GESTURE DETECTOR pour swipe vertical (plier/déplier) et horizontal (mois) return LayoutBuilder(
return GestureDetector( builder: (context, constraints) {
onVerticalDragEnd: (details) { final maxHeight = constraints.maxHeight;
if (details.primaryVelocity != null) {
if (details.primaryVelocity! < -200) { // GESTURE DETECTOR pour swipe vertical (plier/déplier) et horizontal (mois)
// Swipe vers le haut : plier return GestureDetector(
setState(() { onVerticalDragEnd: (details) {
_calendarCollapsed = true; if (details.primaryVelocity != null) {
}); if (details.primaryVelocity! < -200) {
} else if (details.primaryVelocity! > 200) { // Swipe vers le haut : plier
// Swipe vers le bas : déplier setState(() {
setState(() { _calendarCollapsed = true;
_calendarCollapsed = false; });
}); } else if (details.primaryVelocity! > 200) {
} // Swipe vers le bas : déplier
} setState(() {
}, _calendarCollapsed = false;
onHorizontalDragEnd: (details) { });
if (details.primaryVelocity != null) { }
if (details.primaryVelocity! < -200) { }
// Swipe gauche : mois suivant },
final newMonth = onHorizontalDragEnd: (details) {
DateTime(_focusedDay.year, _focusedDay.month + 1, 1); if (details.primaryVelocity != null) {
setState(() { if (details.primaryVelocity! < -200) {
_focusedDay = newMonth; // Swipe gauche : mois suivant
}); final newMonth =
print( DateTime(_focusedDay.year, _focusedDay.month + 1, 1);
'[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); setState(() {
_loadCurrentMonthEvents(); _focusedDay = newMonth;
} else if (details.primaryVelocity! > 200) { });
// Swipe droite : mois précédent print(
final newMonth = '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}');
DateTime(_focusedDay.year, _focusedDay.month - 1, 1); _loadCurrentMonthEvents();
setState(() { } else if (details.primaryVelocity! > 200) {
_focusedDay = newMonth; // Swipe droite : mois précédent
}); final newMonth =
print( DateTime(_focusedDay.year, _focusedDay.month - 1, 1);
'[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); setState(() {
_loadCurrentMonthEvents(); _focusedDay = newMonth;
} });
} print(
}, '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}');
child: Stack( _loadCurrentMonthEvents();
children: [ }
// Calendrier + détails en dessous }
AnimatedPositioned( },
duration: const Duration(milliseconds: 400), child: Stack(
curve: Curves.easeInOut, children: [
top: _calendarCollapsed ? -600 : 0, // cache le calendrier en haut // Calendrier + détails en dessous
left: 0, AnimatedPositioned(
right: 0, duration: const Duration(milliseconds: 400),
height: _calendarCollapsed ? 0 : null, curve: Curves.easeInOut,
child: SizedBox( top: _calendarCollapsed ? -maxHeight : 0, // cache le calendrier en haut
height: MediaQuery.of(context).size.height, left: 0,
child: Column( right: 0,
children: [ height: _calendarCollapsed ? 0 : null,
_buildMonthHeader(context), child: SizedBox(
if (!_calendarCollapsed) height: maxHeight,
// Ajout d'un GestureDetector pour swipe horizontal sur le calendrier child: Column(
GestureDetector( children: [
onHorizontalDragEnd: (details) { _buildMonthHeader(context),
if (details.primaryVelocity != null) { if (!_calendarCollapsed)
if (details.primaryVelocity! < -200) { // Ajout d'un GestureDetector pour swipe horizontal sur le calendrier
// Swipe gauche : mois suivant GestureDetector(
final newMonth = DateTime( onHorizontalDragEnd: (details) {
_focusedDay.year, _focusedDay.month + 1, 1); if (details.primaryVelocity != null) {
setState(() { if (details.primaryVelocity! < -200) {
_focusedDay = newMonth; // Swipe gauche : mois suivant
}); final newMonth = DateTime(
print( _focusedDay.year, _focusedDay.month + 1, 1);
'[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}');
_loadCurrentMonthEvents();
} else if (details.primaryVelocity! > 200) {
// Swipe droite : mois précédent
final newMonth = DateTime(
_focusedDay.year, _focusedDay.month - 1, 1);
setState(() {
_focusedDay = newMonth;
});
print(
'[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}');
_loadCurrentMonthEvents();
}
}
},
child: MobileCalendarView(
focusedDay: _focusedDay,
selectedDay: _selectedDay,
events: filteredEvents,
onDaySelected: (day) {
final eventsForDay = filteredEvents
.where((e) =>
e.startDateTime.year == day.year &&
e.startDateTime.month == day.month &&
e.startDateTime.day == day.day)
.toList()
..sort((a, b) =>
a.startDateTime.compareTo(b.startDateTime));
setState(() {
_selectedDay = day;
_calendarCollapsed = false;
_selectedEventIndex = 0;
_selectedEvent = eventsForDay.isNotEmpty
? eventsForDay[0]
: null;
});
},
),
),
Expanded(
child: hasEvents
// Ajout d'un GestureDetector pour swipe horizontal sur le détail événement
? GestureDetector(
onHorizontalDragEnd: (details) {
if (details.primaryVelocity != null) {
if (details.primaryVelocity! < -200) {
// Swipe gauche : événement suivant
if (_selectedEventIndex <
eventsForSelectedDay.length - 1) {
setState(() {
_selectedEventIndex++;
_selectedEvent = eventsForSelectedDay[
_selectedEventIndex];
});
}
} else if (details.primaryVelocity! > 200) {
// Swipe droite : événement précédent
if (_selectedEventIndex > 0) {
setState(() {
_selectedEventIndex--;
_selectedEvent = eventsForSelectedDay[
_selectedEventIndex];
});
}
}
}
},
child: EventDetails(
event: eventsForSelectedDay[_selectedEventIndex],
selectedDate: _selectedDay,
events: eventsForSelectedDay,
onSelectEvent: (event, date) {
final idx = eventsForSelectedDay
.indexWhere((e) => e.id == event.id);
setState(() { setState(() {
_selectedEventIndex = idx >= 0 ? idx : 0; _focusedDay = newMonth;
_selectedEvent = event;
}); });
}, print(
), '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}');
) _loadCurrentMonthEvents();
: Center( } else if (details.primaryVelocity! > 200) {
child: Text( // Swipe droite : mois précédent
'Aucun événement ne démarre à cette date')), final newMonth = DateTime(
), _focusedDay.year, _focusedDay.month - 1, 1);
], setState(() {
), _focusedDay = newMonth;
), });
), print(
// Vue détail (prend tout l'espace quand calendrier cache) '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}');
if (_calendarCollapsed && _selectedDay != null) _loadCurrentMonthEvents();
AnimatedPositioned( }
duration: const Duration(milliseconds: 400), }
curve: Curves.easeInOut, },
top: _calendarCollapsed ? 0 : 600, child: MobileCalendarView(
left: 0, focusedDay: _focusedDay,
right: 0, selectedDay: _selectedDay,
bottom: 0, events: filteredEvents,
child: SizedBox( onDaySelected: (day) {
height: MediaQuery.of(context).size.height, final eventsForDay = filteredEvents
child: Column( .where((e) =>
children: [ e.startDateTime.year == day.year &&
_buildMonthHeader(context), e.startDateTime.month == day.month &&
Expanded( e.startDateTime.day == day.day)
child: Stack( .toList()
children: [ ..sort((a, b) =>
if (currentEvent != null) a.startDateTime.compareTo(b.startDateTime));
setState(() {
_selectedDay = day;
_calendarCollapsed = false;
_selectedEventIndex = 0;
_selectedEvent = eventsForDay.isNotEmpty
? eventsForDay[0]
: null;
});
},
),
),
Expanded(
child: hasEvents
// Ajout d'un GestureDetector pour swipe horizontal sur le détail événement // Ajout d'un GestureDetector pour swipe horizontal sur le détail événement
GestureDetector( ? GestureDetector(
onHorizontalDragEnd: (details) { onHorizontalDragEnd: (details) {
if (details.primaryVelocity != null) { if (details.primaryVelocity != null) {
if (details.primaryVelocity! < -200) { if (details.primaryVelocity! < -200) {
// Swipe gauche : événement suivant // Swipe gauche : événement suivant
if (_selectedEventIndex < if (_selectedEventIndex <
eventsForSelectedDay.length - 1) { eventsForSelectedDay.length - 1) {
setState(() { setState(() {
_selectedEventIndex++; _selectedEventIndex++;
_selectedEvent = eventsForSelectedDay[ _selectedEvent = eventsForSelectedDay[
_selectedEventIndex]; _selectedEventIndex];
}); });
} }
} else if (details.primaryVelocity! > 200) { } else if (details.primaryVelocity! > 200) {
// Swipe droite : événement précédent // Swipe droite : événement précédent
if (_selectedEventIndex > 0) { if (_selectedEventIndex > 0) {
setState(() { setState(() {
_selectedEventIndex--; _selectedEventIndex--;
_selectedEvent = eventsForSelectedDay[ _selectedEvent = eventsForSelectedDay[
_selectedEventIndex]; _selectedEventIndex];
}); });
}
} }
} }
}
},
child: EventDetails(
event: currentEvent,
selectedDate: _selectedDay,
events: eventsForSelectedDay,
onSelectEvent: (event, date) {
final idx = eventsForSelectedDay
.indexWhere((e) => e.id == event.id);
setState(() {
_selectedEventIndex = idx >= 0 ? idx : 0;
_selectedEvent = event;
});
}, },
), child: EventDetails(
), event: eventsForSelectedDay[_selectedEventIndex],
if (!hasEvents) selectedDate: _selectedDay,
const Center( events: eventsForSelectedDay,
child: Text( onSelectEvent: (event, date) {
'Aucun événement ne démarre à cette date'), final idx = eventsForSelectedDay
), .indexWhere((e) => e.id == event.id);
], setState(() {
_selectedEventIndex = idx >= 0 ? idx : 0;
_selectedEvent = event;
});
},
),
)
: Center(
child: Text(
'Aucun événement ne démarre à cette date')),
), ),
), ],
], ),
), ),
), ),
), // Vue détail (prend tout l'espace quand calendrier cache)
], if (_calendarCollapsed && _selectedDay != null)
), AnimatedPositioned(
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
top: _calendarCollapsed ? 0 : maxHeight,
left: 0,
right: 0,
bottom: 0,
child: SizedBox(
height: maxHeight,
child: Column(
children: [
_buildMonthHeader(context),
Expanded(
child: Stack(
children: [
if (currentEvent != null)
// Ajout d'un GestureDetector pour swipe horizontal sur le détail événement
GestureDetector(
onHorizontalDragEnd: (details) {
if (details.primaryVelocity != null) {
if (details.primaryVelocity! < -200) {
// Swipe gauche : événement suivant
if (_selectedEventIndex <
eventsForSelectedDay.length - 1) {
setState(() {
_selectedEventIndex++;
_selectedEvent = eventsForSelectedDay[
_selectedEventIndex];
});
}
} else if (details.primaryVelocity! > 200) {
// Swipe droite : événement précédent
if (_selectedEventIndex > 0) {
setState(() {
_selectedEventIndex--;
_selectedEvent = eventsForSelectedDay[
_selectedEventIndex];
});
}
}
}
},
child: EventDetails(
event: currentEvent,
selectedDate: _selectedDay,
events: eventsForSelectedDay,
onSelectEvent: (event, date) {
final idx = eventsForSelectedDay
.indexWhere((e) => e.id == event.id);
setState(() {
_selectedEventIndex = idx >= 0 ? idx : 0;
_selectedEvent = event;
});
},
),
),
if (!hasEvents)
const Center(
child: Text(
'Aucun événement ne démarre à cette date'),
),
],
),
),
],
),
),
),
],
),
);
},
); );
} }
+6 -8
View File
@@ -57,6 +57,12 @@ class _EquipmentFormPageState extends State<EquipmentFormPage> {
final provider = Provider.of<EquipmentProvider>(context, listen: false); final provider = Provider.of<EquipmentProvider>(context, listen: false);
provider.loadBrands(); provider.loadBrands();
provider.loadModels(); provider.loadModels();
if (widget.equipment != null) {
if (_selectedBrand != null && _selectedBrand!.isNotEmpty) {
_loadFilteredModels(_selectedBrand!);
}
_loadFilteredSubCategories(_selectedCategory);
}
}); });
if (widget.equipment != null) { if (widget.equipment != null) {
_populateFields(); _populateFields();
@@ -84,14 +90,6 @@ class _EquipmentFormPageState extends State<EquipmentFormPage> {
}); });
DebugLog.info('[EquipmentForm] Populating fields for equipment: ${equipment.id}'); DebugLog.info('[EquipmentForm] Populating fields for equipment: ${equipment.id}');
if (_selectedBrand != null && _selectedBrand!.isNotEmpty) {
_loadFilteredModels(_selectedBrand!);
}
// Charger les sous-catégories pour la catégorie sélectionnée
_loadFilteredSubCategories(_selectedCategory);
} }
@@ -500,12 +500,7 @@ class _EquipmentManagementPageState extends State<EquipmentManagementPage>
return ListView.builder( return ListView.builder(
controller: _scrollController, controller: _scrollController,
itemCount: itemCount, itemCount: itemCount,
// ✅ prototypeItem utilisé car les cartes ont des hauteurs variables : // ✅ Augmenter le cache pour un scroll plus fluide (prototypeItem retiré car les hauteurs dynamiques varient selon le type d'équipement)
// - Les équipements standards (ListTile + margin) font ~88px
// - Les consommables/câbles affichent _buildQuantityDisplay en plus (~30px)
// - prototypeItem permet à Flutter d'optimiser le scroll sans couper les items
prototypeItem: const SizedBox(height: 88),
// ✅ Augmenter le cache pour un scroll plus fluide
cacheExtent: 500, // Précharger 500px en plus cacheExtent: 500, // Précharger 500px en plus
itemBuilder: (context, index) { itemBuilder: (context, index) {
// Dernier élément = indicateur de chargement // Dernier élément = indicateur de chargement
File diff suppressed because it is too large Load Diff