timetable performance optimization

This commit is contained in:
2026-09-25 22:27:10 +02:00
parent ef7d17033d
commit ed8e52f475
13 changed files with 391 additions and 137 deletions
@@ -55,6 +55,13 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
// the same identity checks the cache always used.
(int, TimetableSettings, List<CustomTimetableEvent>, bool)? _cacheKey;
// Stable identities let the calendar reuse its per-week pages across
// rebuilds; rebuilding these every frame would invalidate that cache.
LessonPeriodSchedule? _schedule;
Object? _scheduleKey;
List<TimeRegion>? _regions;
Object? _regionsKey;
DateTime _initialDisplayDate() => DateTime.now().addDays(2);
/// Snaps the calendar back to the current week. Exposed so host pages can
@@ -90,7 +97,37 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
).build();
}
bool _isCrossedOut(Appointment appointment) {
LessonPeriodSchedule _scheduleFor(TimetableState state) {
if (_schedule != null && identical(_scheduleKey, state.timegrid)) {
return _schedule!;
}
_scheduleKey = state.timegrid;
return _schedule = LessonPeriodSchedule.fromState(state);
}
List<TimeRegion> _regionsFor(
TimetableState state,
LessonPeriodSchedule schedule,
) {
final theme = Theme.of(context);
final key = (
state.schoolHolidays,
schedule,
theme.colorScheme,
theme.disabledColor,
DateTime.now().dateOnly,
);
if (_regions != null && key == _regionsKey) return _regions!;
_regionsKey = key;
return _regions = SpecialRegionsBuilder(
holidays: state.schoolHolidays!,
schedule: schedule,
colorScheme: theme.colorScheme,
disabledColor: theme.disabledColor,
).build();
}
static bool _isCrossedOut(Appointment appointment) {
final id = appointment.id;
if (id is LessonAppointment) return id.entry.status == 'CANCELLED';
return false;
@@ -104,14 +141,9 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
// transient null can never crash the build.
if (state.schoolHolidays == null) return const SizedBox.shrink();
final schedule = LessonPeriodSchedule.fromState(state);
final schedule = _scheduleFor(state);
final appointments = _appointments(state);
final regions = SpecialRegionsBuilder(
holidays: state.schoolHolidays!,
schedule: schedule,
colorScheme: Theme.of(context).colorScheme,
disabledColor: Theme.of(context).disabledColor,
).build();
final regions = _regionsFor(state, schedule);
final capabilities = context.watch<CapabilitiesCubit>();
final (minDate, maxDate) = _scrollBounds(
@@ -171,8 +203,8 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
? state.accessibleEndDate!
: baseMax)
: baseMax;
final today = _startOfDay(DateTime.now());
final todayMonday = _mondayOf(today);
final today = DateTime.now().dateOnly;
final todayMonday = today.mondayOfWeek;
final currentWeekEnd = todayMonday.addDays(DateTime.daysPerWeek - 1);
final capMin = pastDays == null
? null
@@ -202,11 +234,6 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
return (mondayMin, finalMax);
}
static DateTime _mondayOf(DateTime d) =>
_startOfDay(d.subtractDays(d.weekday - 1));
static DateTime _startOfDay(DateTime d) => DateTime(d.year, d.month, d.day);
static DateTime _earlier(DateTime a, DateTime b) => a.isBefore(b) ? a : b;
static DateTime _later(DateTime a, DateTime b) => a.isAfter(b) ? a : b;