refactored teacher display in lesson details to better handle substitutions and duplicates

This commit is contained in:
2026-07-16 21:44:08 +02:00
parent e5f7cf0176
commit 8128cede21
@@ -87,7 +87,7 @@ class LessonSheet {
.toList(), .toList(),
), ),
_roomTile(context, lesson), _roomTile(context, lesson),
_teacherTile(lesson), _teacherTile(context, lesson),
if (lesson.classNames.isNotEmpty) if (lesson.classNames.isNotEmpty)
_listTile( _listTile(
icon: Icons.people, icon: Icons.people,
@@ -127,7 +127,7 @@ class LessonSheet {
); );
} }
static Widget _teacherTile(McTimetableEntry lesson) { static Widget _teacherTile(BuildContext context, McTimetableEntry lesson) {
if (lesson.teachers.isEmpty) { if (lesson.teachers.isEmpty) {
return const ListTile( return const ListTile(
leading: Icon(Icons.person), leading: Icon(Icons.person),
@@ -135,33 +135,32 @@ class LessonSheet {
); );
} }
final entries = lesson.teachers.map((t) { // Webuntis liefert entfallende Lehrkräfte bei Vertretungen teils mehrfach —
final shortName = t.shortName.isEmpty ? '?' : t.shortName; // über die Anzeigewerte deduplizieren.
final longName = t.displayName.trim(); final seen = <String>{};
final orgShort = (t.originalShortName ?? '').trim(); final teachers = <_TeacherDisplay>[];
final orgLong = (t.originalDisplayName ?? '').trim(); for (final t in lesson.teachers) {
final display = _TeacherDisplay.from(t);
if (seen.add(display.dedupKey)) teachers.add(display);
}
final subLines = <String>[]; final label = teachers.length == 1 ? 'Lehrkraft' : 'Lehrkräfte';
if (longName.isNotEmpty && longName != shortName) {
subLines.add(longName);
}
if (orgShort.isNotEmpty) {
final label = orgLong.isEmpty || orgLong == orgShort
? orgShort
: '$orgShort · $orgLong';
subLines.add('ehemals $label');
}
return ( // Einzelne, reguläre Lehrkraft kompakt in der Titelzeile.
main: shortName, if (teachers.length == 1 && teachers.first.isPlain) {
sub: subLines.isEmpty ? null : subLines.join('\n'), return ListTile(
leading: const Icon(Icons.person),
title: Text('$label: ${teachers.first.after}'),
); );
}).toList(); }
return _listTileWithSubs( return ListTile(
icon: Icons.person, leading: const Icon(Icons.person),
label: lesson.teachers.length == 1 ? 'Lehrkraft' : 'Lehrkräfte', title: Text(label),
entries: entries, subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [for (final t in teachers) t.buildRow(context)],
),
); );
} }
@@ -318,3 +317,86 @@ class LessonSheet {
} }
} }
} }
/// Aufbereitete Darstellung einer einzelnen Lehrkraft aus einem
/// Webuntis-Element. Trennt die drei Fälle regulär / Vertretung / Entfall,
/// damit die Ansicht statt eines nackten `?` einen sprechenden Namen zeigt.
class _TeacherDisplay {
/// Ersetzte bzw. entfallende Lehrkraft — steht vorn und wird durchgestrichen.
/// Leer bei einer regulären Lehrkraft.
final String before;
/// Aktuelle Lehrkraft. Leer bei ersatzlosem Entfall.
final String after;
const _TeacherDisplay._({this.before = '', this.after = ''});
bool get isPlain => before.isEmpty;
String get dedupKey => '$before|$after';
factory _TeacherDisplay.from(McTimetableTeacher t) {
final current = _formatName(t.shortName, t.displayName);
final original = _formatName(
t.originalShortName ?? '',
t.originalDisplayName ?? '',
);
// Kein (abweichendes) Original → reguläre Lehrkraft.
if (original.isEmpty || original == current) {
return _TeacherDisplay._(after: current.isEmpty ? '?' : current);
}
// Original vorhanden → ersetzte/entfallende Lehrkraft vorn, aktuelle
// Lehrkraft (falls vorhanden) als Ersatz dahinter.
return _TeacherDisplay._(before: original, after: current);
}
/// Eine Bullet-Zeile je Lehrkraft: ersetzte Person durchgestrichen, bei
/// Ersatz ein Pfeil auf die neue Person. Bricht bei Bedarf sauber um.
Widget buildRow(BuildContext context) {
final muted = Theme.of(context).colorScheme.onSurfaceVariant;
final spans = <InlineSpan>[];
if (before.isNotEmpty) {
spans.add(
TextSpan(
text: before,
style: TextStyle(
decoration: TextDecoration.lineThrough,
color: muted,
),
),
);
}
if (after.isNotEmpty) {
if (before.isNotEmpty) {
spans.add(TextSpan(text: '', style: TextStyle(color: muted)));
}
spans.add(TextSpan(text: after));
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(''),
Expanded(child: Text.rich(TextSpan(children: spans))),
],
),
);
}
/// Kombiniert Kurz- und Langname zu „Langname (Kurzname)“ und lässt leere
/// Teile weg. Liefert `''`, wenn beides leer ist.
static String _formatName(String short, String long) {
short = short.trim();
long = long.trim();
// Ein wörtliches „?“ zählt wie ein fehlender Wert.
if (short == '?') short = '';
if (long == '?') long = '';
if (long.isNotEmpty && short.isNotEmpty && long != short) {
return '$long ($short)';
}
if (long.isNotEmpty) return long;
return short;
}
}