bugfix: resolve runtime issues - encoding accents, ListView dynamic heights, notifyListeners exceptions during build phase, and layout overflows
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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
@@ -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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user