show classname instead of teacher name in teacher timetable view

This commit is contained in:
2026-08-09 12:19:57 +02:00
parent 39c16bd4ea
commit 889d8f67c5
21 changed files with 292 additions and 81 deletions
@@ -0,0 +1,19 @@
import '../../../../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
final RegExp _whitespaceRun = RegExp(r'\s+');
/// Collapses any line-break or whitespace run to a single space and trims.
/// Returns null when input is null or fully whitespace. Webuntis sometimes
/// returns multi-line values like "A30\n4" — this normalizes those so labels
/// render on a single line.
String? collapseWhitespace(String? s) {
if (s == null) return null;
final cleaned = s.replaceAll(_whitespaceRun, ' ').trim();
return cleaned.isEmpty ? null : cleaned;
}
/// "7a, 7b" — shared by the calendar tile factory and the home-widget mapper
/// so both surfaces render identical class labels on teacher plans.
extension LessonClassLabel on McTimetableEntry {
String? get classLabel => collapseWhitespace(classNames.join(', '));
}
@@ -1,3 +1,5 @@
import 'package:flutter/foundation.dart';
import '../../../../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
/// Combines back-to-back lessons with identical subject/room/teacher/status
@@ -44,6 +46,9 @@ class LessonMerger {
b.teachers.firstOrNull?.shortName) {
return false;
}
// Relevant für Lehrerpläne: gleicher Lehrer/Fach/Raum, aber verschiedene
// Klassen dürfen nicht zu einem Block verschmelzen.
if (!listEquals(a.classNames, b.classNames)) return false;
if (a.status != b.status) return false;
// Lower bound on the gap — without it, two identical-metadata lessons that
// overlap in time would silently collapse into one.
@@ -8,6 +8,7 @@ import '../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart'
import '../../../../storage/timetable_settings.dart';
import 'arbitrary_appointment.dart';
import 'lesson_color.dart';
import 'lesson_labels.dart';
import 'lesson_merger.dart';
import 'lesson_status.dart';
import 'lesson_type_label.dart';
@@ -23,6 +24,10 @@ class TimetableAppointmentFactory {
final TimetableSettings settings;
final DateTime now;
/// Teacher plans (a teacher's own plan or a foreign teacher view) show the
/// class on the tile instead of the teacher's own name.
final bool showClassInsteadOfTeacher;
TimetableAppointmentFactory({
required this.lessons,
required this.customEvents,
@@ -30,6 +35,7 @@ class TimetableAppointmentFactory {
required this.settings,
required this.now,
this.holidays = const [],
this.showClassInsteadOfTeacher = false,
});
List<Appointment> build() {
@@ -130,7 +136,7 @@ class TimetableAppointmentFactory {
location: event.description.trim().isEmpty
? null
: event.description.trim(),
subject: _collapseWhitespace(event.title) ?? event.title,
subject: collapseWhitespace(event.title) ?? event.title,
recurrenceRule: parsed.rule,
recurrenceExceptionDates: exceptionDates.isEmpty ? null : exceptionDates,
color:
@@ -222,7 +228,7 @@ class TimetableAppointmentFactory {
TimetableNameMode.longName => lookup?.longName ?? subjectShort,
TimetableNameMode.alternateName => lookup?.longName ?? subjectShort,
};
final collapsed = _collapseWhitespace(name);
final collapsed = collapseWhitespace(name);
if (collapsed != null) return collapsed;
}
// Subject leer → Titel aus dem Lesson-Type ableiten. Pausenaufsicht etc.
@@ -233,10 +239,13 @@ class TimetableAppointmentFactory {
String _locationLabel(McTimetableEntry lesson) {
final roomName =
_collapseWhitespace(lesson.rooms.firstOrNull) ?? 'Unbekannt';
final teacherName =
_teacherLabel(lesson.teachers.firstOrNull) ?? 'Unbekannt';
return '$roomName\n$teacherName';
collapseWhitespace(lesson.rooms.firstOrNull) ?? 'Unbekannt';
// Klassenlose Einträge (Aufsichten etc.) fallen auf den Lehrer zurück.
final secondLine =
(showClassInsteadOfTeacher ? lesson.classLabel : null) ??
_teacherLabel(lesson.teachers.firstOrNull) ??
'Unbekannt';
return '$roomName\n$secondLine';
}
/// Backend serves teachers with their full display name ("Stefan Müller"),
@@ -245,27 +254,11 @@ class TimetableAppointmentFactory {
/// overview; the detail sheet still renders the full name as a subtitle.
static String? _teacherLabel(McTimetableTeacher? teacher) {
if (teacher == null) return null;
final display = _collapseWhitespace(teacher.displayName);
final display = collapseWhitespace(teacher.displayName);
if (display != null && display.isNotEmpty) {
final parts = display.split(' ');
return parts.isEmpty ? display : parts.last;
}
return _collapseWhitespace(teacher.shortName);
}
/// Collapses any line-break or whitespace run to a single space and trims.
/// Returns null when input is null or fully whitespace. Webuntis sometimes
/// returns multi-line room names like "A30\n4" — this normalizes those so
/// the tile renders the room on a single line.
static String? _collapseWhitespace(String? s) {
if (s == null) return null;
final cleaned = s
.replaceAll('\r\n', ' ')
.replaceAll('\n', ' ')
.replaceAll('\r', ' ')
.replaceAll('\t', ' ')
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
return cleaned.isEmpty ? null : cleaned;
return collapseWhitespace(teacher.shortName);
}
}