bugfix: resolve runtime issues - encoding accents, ListView dynamic heights, notifyListeners exceptions during build phase, and layout overflows
This commit is contained in:
+228
-222
@@ -980,237 +980,243 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
? eventsForSelectedDay[_selectedEventIndex]
|
||||
: null;
|
||||
|
||||
// GESTURE DETECTOR pour swipe vertical (plier/déplier) et horizontal (mois)
|
||||
return GestureDetector(
|
||||
onVerticalDragEnd: (details) {
|
||||
if (details.primaryVelocity != null) {
|
||||
if (details.primaryVelocity! < -200) {
|
||||
// Swipe vers le haut : plier
|
||||
setState(() {
|
||||
_calendarCollapsed = true;
|
||||
});
|
||||
} 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 =
|
||||
DateTime(_focusedDay.year, _focusedDay.month + 1, 1);
|
||||
setState(() {
|
||||
_focusedDay = newMonth;
|
||||
});
|
||||
print(
|
||||
'[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: Stack(
|
||||
children: [
|
||||
// Calendrier + détails en dessous
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
top: _calendarCollapsed ? -600 : 0, // cache le calendrier en haut
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: _calendarCollapsed ? 0 : null,
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildMonthHeader(context),
|
||||
if (!_calendarCollapsed)
|
||||
// Ajout d'un GestureDetector pour swipe horizontal sur le calendrier
|
||||
GestureDetector(
|
||||
onHorizontalDragEnd: (details) {
|
||||
if (details.primaryVelocity != null) {
|
||||
if (details.primaryVelocity! < -200) {
|
||||
// Swipe gauche : mois suivant
|
||||
final newMonth = DateTime(
|
||||
_focusedDay.year, _focusedDay.month + 1, 1);
|
||||
setState(() {
|
||||
_focusedDay = newMonth;
|
||||
});
|
||||
print(
|
||||
'[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);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxHeight = constraints.maxHeight;
|
||||
|
||||
// GESTURE DETECTOR pour swipe vertical (plier/déplier) et horizontal (mois)
|
||||
return GestureDetector(
|
||||
onVerticalDragEnd: (details) {
|
||||
if (details.primaryVelocity != null) {
|
||||
if (details.primaryVelocity! < -200) {
|
||||
// Swipe vers le haut : plier
|
||||
setState(() {
|
||||
_calendarCollapsed = true;
|
||||
});
|
||||
} 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 =
|
||||
DateTime(_focusedDay.year, _focusedDay.month + 1, 1);
|
||||
setState(() {
|
||||
_focusedDay = newMonth;
|
||||
});
|
||||
print(
|
||||
'[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: Stack(
|
||||
children: [
|
||||
// Calendrier + détails en dessous
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
top: _calendarCollapsed ? -maxHeight : 0, // cache le calendrier en haut
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: _calendarCollapsed ? 0 : null,
|
||||
child: SizedBox(
|
||||
height: maxHeight,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildMonthHeader(context),
|
||||
if (!_calendarCollapsed)
|
||||
// Ajout d'un GestureDetector pour swipe horizontal sur le calendrier
|
||||
GestureDetector(
|
||||
onHorizontalDragEnd: (details) {
|
||||
if (details.primaryVelocity != null) {
|
||||
if (details.primaryVelocity! < -200) {
|
||||
// Swipe gauche : mois suivant
|
||||
final newMonth = DateTime(
|
||||
_focusedDay.year, _focusedDay.month + 1, 1);
|
||||
setState(() {
|
||||
_selectedEventIndex = idx >= 0 ? idx : 0;
|
||||
_selectedEvent = event;
|
||||
_focusedDay = newMonth;
|
||||
});
|
||||
},
|
||||
),
|
||||
)
|
||||
: 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 : 600,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildMonthHeader(context),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
if (currentEvent != null)
|
||||
print(
|
||||
'[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];
|
||||
});
|
||||
? 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'),
|
||||
),
|
||||
],
|
||||
child: EventDetails(
|
||||
event: eventsForSelectedDay[_selectedEventIndex],
|
||||
selectedDate: _selectedDay,
|
||||
events: eventsForSelectedDay,
|
||||
onSelectEvent: (event, 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);
|
||||
provider.loadBrands();
|
||||
provider.loadModels();
|
||||
if (widget.equipment != null) {
|
||||
if (_selectedBrand != null && _selectedBrand!.isNotEmpty) {
|
||||
_loadFilteredModels(_selectedBrand!);
|
||||
}
|
||||
_loadFilteredSubCategories(_selectedCategory);
|
||||
}
|
||||
});
|
||||
if (widget.equipment != null) {
|
||||
_populateFields();
|
||||
@@ -84,14 +90,6 @@ class _EquipmentFormPageState extends State<EquipmentFormPage> {
|
||||
});
|
||||
|
||||
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(
|
||||
controller: _scrollController,
|
||||
itemCount: itemCount,
|
||||
// ✅ prototypeItem utilisé car les cartes ont des hauteurs variables :
|
||||
// - 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
|
||||
// ✅ Augmenter le cache pour un scroll plus fluide (prototypeItem retiré car les hauteurs dynamiques varient selon le type d'équipement)
|
||||
cacheExtent: 500, // Précharger 500px en plus
|
||||
itemBuilder: (context, index) {
|
||||
// Dernier élément = indicateur de chargement
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user