diff --git a/lib/view/pages/timetable/data/lesson_type_label.dart b/lib/view/pages/timetable/data/lesson_type_label.dart index e5aa2c2..3b4f265 100644 --- a/lib/view/pages/timetable/data/lesson_type_label.dart +++ b/lib/view/pages/timetable/data/lesson_type_label.dart @@ -1,4 +1,5 @@ import '../../../../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart'; +import 'lesson_labels.dart'; /// Derives a human-readable title from the Marianum-Connect `lessonType` for /// lessons that have no subject of their own (Aufsicht, Sprechstunde, …). @@ -10,10 +11,20 @@ class LessonTypeLabel { static String forEntry(McTimetableEntry lesson) { final base = baseLabel(lesson.lessonType); + if (base == fallback) { + final text = entryText(lesson); + if (text != null) return text; + } final room = (lesson.rooms.firstOrNull ?? '').trim(); return room.isEmpty ? base : '$room $base'; } + /// Webuntis-Freitext eines fachlosen Eintrags („Klassenfahrt 8a“), der + /// aussagekräftiger ist als das generische „Event“. + static String? entryText(McTimetableEntry lesson) => + collapseWhitespace(lesson.substitutionText) ?? + collapseWhitespace(lesson.lessonText); + /// Returns just the type wording without the room — useful when the caller /// renders the room separately (e.g. as a subtitle line). static String baseLabel(String lessonType) { diff --git a/lib/view/pages/timetable/details/lesson_sheet.dart b/lib/view/pages/timetable/details/lesson_sheet.dart index 5f0d5f4..bcd399c 100644 --- a/lib/view/pages/timetable/details/lesson_sheet.dart +++ b/lib/view/pages/timetable/details/lesson_sheet.dart @@ -10,6 +10,7 @@ import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart'; import '../../../../state/app/modules/timetable/bloc/timetable_state.dart'; import '../../../../widget/debug/debug_tile.dart'; import '../../../../widget/details_bottom_sheet.dart'; +import '../data/lesson_labels.dart'; import '../data/lesson_type_label.dart'; import '../subject_colors/subject_color_picker.dart'; @@ -226,10 +227,13 @@ class LessonSheet { } static List _optionalTextTiles(McTimetableEntry lesson) { + final sameText = + collapseWhitespace(lesson.lessonText) == + collapseWhitespace(lesson.substitutionText); return [ _textTile(Icons.info_outline, 'Info', lesson.infoText), _textTile(Icons.swap_horiz, 'Vertretungstext', lesson.substitutionText), - _textTile(Icons.subject, 'Stundentext', lesson.lessonText), + if (!sameText) _textTile(Icons.subject, 'Stundentext', lesson.lessonText), _textTile( Icons.category_outlined, 'Stundentyp', diff --git a/lib/widget_data/widget_data_mapper.dart b/lib/widget_data/widget_data_mapper.dart index af8a063..080a444 100644 --- a/lib/widget_data/widget_data_mapper.dart +++ b/lib/widget_data/widget_data_mapper.dart @@ -14,6 +14,7 @@ import '../view/pages/timetable/data/lesson_labels.dart'; import '../view/pages/timetable/data/lesson_merger.dart'; import '../view/pages/timetable/data/lesson_period_schedule.dart'; import '../view/pages/timetable/data/lesson_status.dart'; +import '../view/pages/timetable/data/lesson_type_label.dart'; import 'widget_data.dart'; class WidgetDataMapper { @@ -326,9 +327,11 @@ class WidgetDataMapper { LessonStatusClassifier.classify(lesson, start, end, now), ); final subjectShortRaw = lesson.subjects.firstOrNull?.trim() ?? ''; - // Webuntis sometimes ships subject-less entries (Wandertag etc.). Fall - // back to "Event" so the tile isn't just a dash. - final subjectShort = subjectShortRaw.isEmpty ? 'Event' : subjectShortRaw; + // Webuntis sometimes ships subject-less entries (Wandertag etc.). Prefer + // their free text, else "Event" so the tile isn't just a dash. + final subjectShort = subjectShortRaw.isEmpty + ? LessonTypeLabel.entryText(lesson) ?? LessonTypeLabel.fallback + : subjectShortRaw; String? subjectLong; if (subjects != null && subjectShortRaw.isNotEmpty) { subjectLong = subjects.result diff --git a/test/view/timetable/lesson_type_label_test.dart b/test/view/timetable/lesson_type_label_test.dart new file mode 100644 index 0000000..86b0f2c --- /dev/null +++ b/test/view/timetable/lesson_type_label_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart'; +import 'package:marianum_mobile/view/pages/timetable/data/lesson_type_label.dart'; + +McTimetableEntry _entry({ + String lessonType = 'LESSON', + String? substitutionText, + String? lessonText, +}) => McTimetableEntry( + id: 1, + date: DateTime(2026, 5, 4), + startTime: DateTime(1970, 1, 1, 8), + endTime: DateTime(1970, 1, 1, 15), + subjects: const [], + teachers: const [], + rooms: const ['A101'], + classNames: const ['8a'], + lessonType: lessonType, + status: 'REGULAR', + substitutionText: substitutionText, + lessonText: lessonText, + infoText: null, +); + +void main() { + group('LessonTypeLabel.forEntry', () { + test('prefers the substitution text', () { + expect( + LessonTypeLabel.forEntry( + _entry(substitutionText: 'Klassenfahrt 8a', lessonText: 'Anderes'), + ), + 'Klassenfahrt 8a', + ); + }); + + test('falls back to the lesson text', () { + expect( + LessonTypeLabel.forEntry(_entry(lessonText: ' Wandertag\n8a ')), + 'Wandertag 8a', + ); + }); + + test('falls back to "Event" with room when no text is set', () { + expect( + LessonTypeLabel.forEntry(_entry(substitutionText: ' ')), + 'A101 Event', + ); + }); + + test('keeps known lesson types over free text', () { + expect( + LessonTypeLabel.forEntry( + _entry(lessonType: 'BREAK_SUPERVISION', lessonText: 'Hof'), + ), + 'A101 Aufsicht', + ); + }); + }); +}