simplified code across the app and consolidated duplicated helpers

This commit is contained in:
2026-09-27 17:29:54 +02:00
parent 3e856d0b1c
commit ee1240f71a
155 changed files with 1772 additions and 2546 deletions
+92 -62
View File
@@ -53,6 +53,42 @@ class WidgetDataMapper {
/// native renderers can roll the view forward without fresh data.
static const int weekWindowDays = 14;
/// Day and week payload from the same inputs.
static ({WidgetTimetableData day, WidgetTimetableData week}) buildSnapshot({
required DateTime now,
required Iterable<McTimetableEntry> lessons,
required TimetableGetSubjectsResponse? subjects,
required TimetableGetRoomsResponse? rooms,
required TimetableGetHolidaysResponse? holidays,
TimetableGetTimegridResponse? timegrid,
GetCustomTimetableEventResponse? customEvents,
bool connectDoubleLessons = true,
bool showClassInsteadOfTeacher = false,
}) => (
day: buildDayData(
now: now,
lessons: lessons,
subjects: subjects,
rooms: rooms,
holidays: holidays,
timegrid: timegrid,
customEvents: customEvents,
connectDoubleLessons: connectDoubleLessons,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
),
week: buildWeekData(
now: now,
lessons: lessons,
subjects: subjects,
rooms: rooms,
holidays: holidays,
timegrid: timegrid,
customEvents: customEvents,
connectDoubleLessons: connectDoubleLessons,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
),
);
static WidgetTimetableData buildDayData({
required DateTime now,
required Iterable<McTimetableEntry> lessons,
@@ -68,24 +104,23 @@ class WidgetDataMapper {
final holiday = _findHoliday(anchor, holidays);
final dayStart = anchor;
final dayEnd = anchor.add(const Duration(days: 1));
final dayLessons = lessons.where((l) => _onSameDay(l, anchor)).toList();
final dayLessons = lessons.where((l) => l.date.isSameDay(anchor)).toList();
final source = connectDoubleLessons
? LessonMerger.merge(dayLessons)
: dayLessons;
final mapped = <WidgetLesson>[
..._mapAll(
return WidgetTimetableData(
fetchedAt: now,
anchorDate: anchor,
lessons: _mapWindow(
source,
now,
subjects,
rooms,
customEvents,
dayStart,
dayEnd,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
),
..._expandCustomEvents(customEvents, dayStart, dayEnd),
]..sort((a, b) => a.start.compareTo(b.start));
return WidgetTimetableData(
fetchedAt: now,
anchorDate: anchor,
lessons: _resolveCollisions(mapped),
periods: _resolvePeriods(timegrid),
isHoliday: holiday != null,
holidayName: holiday?.longName,
@@ -118,16 +153,6 @@ class WidgetDataMapper {
final source = connectDoubleLessons
? _mergePerDay(weekLessons)
: weekLessons;
final mapped = <WidgetLesson>[
..._mapAll(
source,
now,
subjects,
rooms,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
),
..._expandCustomEvents(customEvents, windowStart, endExclusive),
]..sort((a, b) => a.start.compareTo(b.start));
final days = [
for (var i = 0; i < weekWindowDays; i++)
_dayInfo(windowStart.addDays(i), holidays),
@@ -141,7 +166,16 @@ class WidgetDataMapper {
return WidgetTimetableData(
fetchedAt: now,
anchorDate: anchor,
lessons: _resolveCollisions(mapped),
lessons: _mapWindow(
source,
now,
subjects,
rooms,
customEvents,
windowStart,
endExclusive,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
),
periods: _resolvePeriods(timegrid),
isHoliday: anchorInfo.isHoliday,
holidayName: anchorInfo.holidayName,
@@ -300,28 +334,38 @@ class WidgetDataMapper {
return [for (final group in byDay.values) ...LessonMerger.merge(group)];
}
static Iterable<WidgetLesson> _mapAll(
/// Lessons plus custom events in [start, endExclusive), sorted by start
/// with overlaps resolved.
static List<WidgetLesson> _mapWindow(
Iterable<McTimetableEntry> source,
DateTime now,
TimetableGetSubjectsResponse? subjects,
TimetableGetRoomsResponse? rooms, {
TimetableGetRoomsResponse? rooms,
GetCustomTimetableEventResponse? customEvents,
DateTime start,
DateTime endExclusive, {
required bool showClassInsteadOfTeacher,
}) => source.map(
(l) => _mapLesson(
l,
now,
subjects,
rooms,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
),
);
}) {
final mapped = <WidgetLesson>[
for (final lesson in source)
_mapLesson(
lesson,
now,
subjects,
rooms,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
),
..._expandCustomEvents(customEvents, start, endExclusive),
]..sort((a, b) => a.start.compareTo(b.start));
return _resolveCollisions(mapped);
}
static WidgetLesson _mapLesson(
McTimetableEntry lesson,
DateTime now,
TimetableGetSubjectsResponse? subjects,
TimetableGetRoomsResponse? rooms, {
bool showClassInsteadOfTeacher = false,
required bool showClassInsteadOfTeacher,
}) {
final start = lesson.startDateTime;
final end = lesson.endDateTime;
@@ -395,43 +439,29 @@ class WidgetDataMapper {
return '#${rgb.toRadixString(16).padLeft(6, '0').toUpperCase()}';
}
static WidgetLessonStatus _mapStatus(LessonStatus status) {
switch (status) {
case LessonStatus.cancelled:
return WidgetLessonStatus.cancelled;
case LessonStatus.event:
return WidgetLessonStatus.event;
case LessonStatus.irregular:
return WidgetLessonStatus.irregular;
case LessonStatus.teacherChanged:
return WidgetLessonStatus.teacherChanged;
case LessonStatus.duty:
return WidgetLessonStatus.duty;
case LessonStatus.past:
return WidgetLessonStatus.past;
case LessonStatus.ongoing:
return WidgetLessonStatus.ongoing;
case LessonStatus.regular:
return WidgetLessonStatus.regular;
}
}
static bool _onSameDay(McTimetableEntry lesson, DateTime day) {
return lesson.date.year == day.year &&
lesson.date.month == day.month &&
lesson.date.day == day.day;
}
static WidgetLessonStatus _mapStatus(LessonStatus status) =>
switch (status) {
LessonStatus.cancelled => WidgetLessonStatus.cancelled,
LessonStatus.event => WidgetLessonStatus.event,
LessonStatus.irregular => WidgetLessonStatus.irregular,
LessonStatus.teacherChanged => WidgetLessonStatus.teacherChanged,
LessonStatus.duty => WidgetLessonStatus.duty,
LessonStatus.past => WidgetLessonStatus.past,
LessonStatus.ongoing => WidgetLessonStatus.ongoing,
LessonStatus.regular => WidgetLessonStatus.regular,
};
static McHoliday? _findHoliday(
DateTime day,
TimetableGetHolidaysResponse? holidays,
) {
if (holidays == null) return null;
final asDay = DateTime(day.year, day.month, day.day);
final asDay = day.dateOnly;
for (final h in holidays.result) {
final start = DateTime(h.startDate.year, h.startDate.month, h.startDate.day);
final end = DateTime(h.endDate.year, h.endDate.month, h.endDate.day);
if (!asDay.isBefore(start) && !asDay.isAfter(end)) return h;
if (!asDay.isBefore(h.startDate.dateOnly) &&
!asDay.isAfter(h.endDate.dateOnly)) {
return h;
}
}
return null;
}
+4 -29
View File
@@ -80,22 +80,10 @@ class WidgetPublisher {
]);
_lastFlags = flags;
}
final lessons = state.getAllKnownLessons();
final now = widgetNow();
final dayData = WidgetDataMapper.buildDayData(
final (day: dayData, week: weekData) = WidgetDataMapper.buildSnapshot(
now: now,
lessons: lessons,
subjects: state.subjects,
rooms: state.rooms,
holidays: state.schoolHolidays,
timegrid: state.timegrid,
customEvents: state.customEvents,
connectDoubleLessons: connectDouble,
showClassInsteadOfTeacher: isTeacher,
);
final weekData = WidgetDataMapper.buildWeekData(
now: now,
lessons: lessons,
lessons: state.getAllKnownLessons(),
subjects: state.subjects,
rooms: state.rooms,
holidays: state.schoolHolidays,
@@ -121,10 +109,7 @@ class WidgetPublisher {
}
_lastSignature = signature;
_lastPublishedAt = now;
await WidgetSync.writeDayData(dayData);
await WidgetSync.writeWeekData(weekData);
await WidgetSync.setLoggedIn(true);
await WidgetSync.triggerUpdate();
await WidgetSync.publishSnapshot(day: dayData, week: weekData);
} on Object catch (e, s) {
// Catch Object: non-Exception Errors (RangeError, StateError) from the
// bloc layer must not escape into the stream listener.
@@ -135,15 +120,5 @@ class WidgetPublisher {
static Map<String, Object?> _withoutFetchedAt(Map<String, Object?> json) =>
Map.of(json)..remove('fetchedAt');
static String _themeName(ThemeMode? mode) {
switch (mode) {
case ThemeMode.light:
return 'light';
case ThemeMode.dark:
return 'dark';
case ThemeMode.system:
case null:
return 'system';
}
}
static String _themeName(ThemeMode? mode) => (mode ?? ThemeMode.system).name;
}
+31 -20
View File
@@ -53,13 +53,21 @@ class WidgetSync {
static Future<void> writeWeekData(WidgetTimetableData data) =>
_writeData(weekDataKey, data);
/// Writes a complete snapshot and marks the widget signed in, then asks the
/// native side to re-render.
static Future<void> publishSnapshot({
required WidgetTimetableData day,
required WidgetTimetableData week,
}) async {
await writeDayData(day);
await writeWeekData(week);
await setLoggedIn(true);
await triggerUpdate();
}
static Future<void> _writeData(String key, WidgetTimetableData data) async {
await ensureInitialized();
await HomeWidget.saveWidgetData<String>(key, jsonEncode(data.toJson()));
await HomeWidget.saveWidgetData<String>(
fetchedAtKey,
data.fetchedAt.toIso8601String(),
);
await _setString(key, jsonEncode(data.toJson()));
await _setString(fetchedAtKey, data.fetchedAt.toIso8601String());
}
static Future<void> setLoggedIn(bool loggedIn) =>
@@ -88,27 +96,30 @@ class WidgetSync {
return await HomeWidget.getWidgetData<bool>(key) ?? defaultValue;
}
static Future<void> setThemeMode(String mode) async {
await ensureInitialized();
await HomeWidget.saveWidgetData<String>(themeModeKey, mode);
}
static Future<void> setThemeMode(String mode) =>
_setString(themeModeKey, mode);
static Future<void> setMarianumConnectBaseUrl(String url) async {
await ensureInitialized();
await HomeWidget.saveWidgetData<String>(marianumConnectBaseUrlKey, url);
}
static Future<void> setMarianumConnectBaseUrl(String url) =>
_setString(marianumConnectBaseUrlKey, url);
static Future<String?> getMarianumConnectBaseUrl() async {
await ensureInitialized();
return HomeWidget.getWidgetData<String>(marianumConnectBaseUrlKey);
}
static Future<String?> getMarianumConnectBaseUrl() =>
_getString(marianumConnectBaseUrlKey);
static Future<DateTime?> getFetchedAt() async {
await ensureInitialized();
final raw = await HomeWidget.getWidgetData<String>(fetchedAtKey);
final raw = await _getString(fetchedAtKey);
return raw == null ? null : DateTime.tryParse(raw);
}
static Future<void> _setString(String key, String? value) async {
await ensureInitialized();
await HomeWidget.saveWidgetData<String>(key, value);
}
static Future<String?> _getString(String key) async {
await ensureInitialized();
return HomeWidget.getWidgetData<String>(key);
}
static Future<void> clear() async {
WidgetPublisher.resetDedupe();
await ensureInitialized();