import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import '../../../../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart'; import '../../../../extensions/date_time.dart'; import '../../../../extensions/text.dart'; import '../../../../routing/app_routes.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_type_label.dart'; class LessonSheet { static void show( BuildContext context, TimetableState? state, Appointment appointment, McTimetableEntry lesson, ) { if (state == null) return; final subjectShort = lesson.subjects.firstOrNull; final headerLong = subjectShort == null ? null : state.subjects?.result .where((s) => s.shortName == subjectShort) .firstOrNull ?.longName; // Bei Stunden ohne Fach (Pausenaufsicht etc.) den Lesson-Type-Titel // einsetzen — sonst stünde im Header nur ein generisches "?". final headerTitle = subjectShort != null ? firstNonEmpty([subjectShort, headerLong, '?']) : LessonTypeLabel.forEntry(lesson); final headerLongName = (headerLong != null && headerLong.isNotEmpty && headerLong != headerTitle) ? headerLong : ''; final timeRange = appointment.startTime.timeRangeTo(appointment.endTime); showDetailsBottomSheet( context, header: ListTile( leading: Icon(_iconForStatus(lesson.status), size: 32), title: Text( '${_statusPrefix(lesson.status)}$headerTitle', style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( headerLongName.isNotEmpty ? '$timeRange\n$headerLongName' : timeRange, ), isThreeLine: headerLongName.isNotEmpty, ), children: (_) => [ ListTile( leading: const Icon(Icons.notifications_active), title: Text('Status: ${_statusLabel(lesson.status)}'), ), if (lesson.subjects.length > 1) _listTile( icon: Icons.book_outlined, label: 'Fächer', entries: lesson.subjects .map( (s) => _line(s, longname: _subjectLongName(state.subjects, s)), ) .toList(), ), _roomTile(context, lesson), _teacherTile(lesson), if (lesson.classNames.isNotEmpty) _listTile( icon: Icons.people, label: lesson.classNames.length == 1 ? 'Klasse' : 'Klassen', entries: lesson.classNames.map(_line).toList(), ), ..._optionalTextTiles(lesson), DebugTile(context).jsonData(lesson.toJson()), ], ); } static Widget _roomTile(BuildContext context, McTimetableEntry lesson) { final trailing = IconButton( icon: const Icon(Icons.house_outlined), onPressed: () => AppRoutes.openRoomplan(context), ); if (lesson.rooms.isEmpty) { return ListTile( leading: const Icon(Icons.room), title: const Text('Raum: ?'), trailing: trailing, ); } final entries = lesson.rooms .map((name) => (main: _line(name), sub: null as String?)) .toList(); return _listTileWithSubs( icon: Icons.room, label: lesson.rooms.length == 1 ? 'Raum' : 'Räume', entries: entries, trailing: trailing, ); } static Widget _teacherTile(McTimetableEntry lesson) { if (lesson.teachers.isEmpty) { return const ListTile( leading: Icon(Icons.person), title: Text('Lehrkraft: ?'), ); } final entries = lesson.teachers.map((t) { final shortName = t.shortName.isEmpty ? '?' : t.shortName; final longName = t.displayName.trim(); final orgShort = (t.originalShortName ?? '').trim(); final orgLong = (t.originalDisplayName ?? '').trim(); final subLines = []; 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 ( main: shortName, sub: subLines.isEmpty ? null : subLines.join('\n'), ); }).toList(); return _listTileWithSubs( icon: Icons.person, label: lesson.teachers.length == 1 ? 'Lehrkraft' : 'Lehrkräfte', entries: entries, ); } static Widget _listTileWithSubs({ required IconData icon, required String label, required List<({String main, String? sub})> entries, Widget? trailing, }) { if (entries.length == 1) { final e = entries.first; return ListTile( leading: Icon(icon), title: Text('$label: ${e.main}'), subtitle: e.sub != null ? Text(e.sub!) : null, trailing: trailing, ); } return ListTile( leading: Icon(icon), title: Text(label), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: entries .expand( (e) => [ Text(e.main), if (e.sub != null) Padding( padding: const EdgeInsets.only(left: 12), child: Text(e.sub!), ), ], ) .toList(), ), trailing: trailing, ); } static Widget _listTile({ required IconData icon, required String label, required List entries, Widget? trailing, }) { if (entries.length == 1) { return ListTile( leading: Icon(icon), title: Text('$label: ${entries.first}'), trailing: trailing, ); } return ListTile( leading: Icon(icon), title: Text(label), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: entries.map(Text.new).toList(), ), trailing: trailing, ); } static List _optionalTextTiles(McTimetableEntry lesson) { return [ _textTile(Icons.info_outline, 'Info', lesson.infoText), _textTile(Icons.swap_horiz, 'Vertretungstext', lesson.substitutionText), _textTile(Icons.subject, 'Stundentext', lesson.lessonText), _textTile( Icons.category_outlined, 'Stundentyp', _lessonTypeLabel(lesson.lessonType), ), ].whereType().toList(); } /// Marianum-Connect liefert den Stundentyp immer (Default `LESSON`). Den /// Standard blenden wir aus — sonst stünde unter jeder regulären Stunde /// derselbe Eintrag. Sonderfälle bekommen einen deutschen Klartext. static String? _lessonTypeLabel(String type) { switch (type) { case 'LESSON': return null; case 'OFFICE_HOUR': return 'Sprechstunde'; case 'STANDBY': return 'Bereitschaft'; case 'BREAK_SUPERVISION': return 'Pausenaufsicht'; case 'EXAM': return 'Prüfung'; default: return type; } } static Widget? _textTile(IconData icon, String label, String? value) { final text = (value ?? '').trim(); if (text.isEmpty || text == '-') return null; return ListTile( leading: Icon(icon), title: Text(label), subtitle: Text(text), ); } static String _line(String name, {String? longname, String? extra}) { final parts = [if (name.isNotEmpty) name else '?']; final ln = (longname ?? '').trim(); if (ln.isNotEmpty && ln != name) parts.add('($ln)'); final ex = (extra ?? '').trim(); if (ex.isNotEmpty) parts.add('· $ex'); return parts.join(' '); } static String? _subjectLongName(dynamic subjects, String shortName) { if (subjects == null) return null; final list = subjects.result as Iterable; for (final s in list) { if (s.shortName == shortName) return s.longName as String?; } return null; } static IconData _iconForStatus(String status) { switch (status) { case 'CANCELLED': return Icons.event_busy_outlined; case 'IRREGULAR': return Icons.swap_horiz; default: return Icons.school_outlined; } } static String _statusLabel(String status) { switch (status) { case 'CANCELLED': return 'Entfällt'; case 'IRREGULAR': return 'Geändert'; default: return 'Regulär'; } } static String _statusPrefix(String status) { switch (status) { case 'CANCELLED': return 'Entfällt: '; case 'IRREGULAR': return 'Änderung: '; default: return ''; } } }