Files
Client/lib/view/pages/timetable/details/webuntis_lesson_sheet.dart
T
2026-05-08 20:12:40 +02:00

222 lines
6.9 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import '../../../../api/webuntis/queries/get_timetable/get_timetable_response.dart';
import '../../../../api/webuntis/services/lesson_resolver.dart';
import '../../../../extensions/date_time.dart';
import '../../../../extensions/text.dart';
import '../../../../routing/app_routes.dart';
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 '../../../../widget/unimplemented_dialog.dart';
class WebuntisLessonSheet {
static void show(
BuildContext context,
TimetableBloc bloc,
Appointment appointment,
GetTimetableResponseObject lesson,
) {
final state = bloc.state.data;
if (state == null) return;
final headerSubject = LessonResolver.resolveSubject(
state,
lesson.su.firstOrNull?.id,
);
final headerTitle = firstNonEmpty([
headerSubject.alternateName,
headerSubject.name,
headerSubject.longName,
'?',
]);
final headerLongName =
headerSubject.longName.isNotEmpty &&
headerSubject.longName != headerTitle
? headerSubject.longName
: '';
final timeRange = appointment.startTime.timeRangeTo(appointment.endTime);
showDetailsBottomSheet(
context,
header: ListTile(
leading: Icon(LessonFormatter.iconForCode(lesson.code), size: 32),
title: Text(
'${LessonFormatter.codePrefix(lesson.code)}$headerTitle',
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
headerLongName.isNotEmpty ? '$timeRange\n$headerLongName' : timeRange,
),
isThreeLine: headerLongName.isNotEmpty,
),
children: (_) => <Widget>[
ListTile(
leading: const Icon(Icons.notifications_active),
title: Text('Status: ${LessonFormatter.statusLabel(lesson.code)}'),
),
if (lesson.su.length > 1)
_listTile(
icon: Icons.book_outlined,
label: 'Fächer',
entries: lesson.su.map((s) {
final resolved = LessonResolver.resolveSubject(state, s.id);
return LessonFormatter.formatLine(
firstNonEmpty([resolved.name, s.name, '?']),
longname: firstNonEmpty([resolved.longName, s.longname, '']),
);
}).toList(),
),
_roomTile(context, state, lesson),
_teacherTile(context, lesson),
if ((lesson.activityType ?? '').trim().isNotEmpty)
ListTile(
leading: const Icon(Icons.abc),
title: Text('Typ: ${lesson.activityType}'),
),
if (lesson.kl.isNotEmpty)
_listTile(
icon: Icons.people,
label: lesson.kl.length == 1 ? 'Klasse' : 'Klassen',
entries: lesson.kl
.map(
(k) => LessonFormatter.formatLine(
k.name.isNotEmpty ? k.name : '?',
longname: k.longname,
),
)
.toList(),
),
..._optionalTextTiles(lesson),
DebugTile(context).jsonData(lesson.toJson()),
],
);
}
static Widget _roomTile(
BuildContext context,
TimetableState state,
GetTimetableResponseObject lesson,
) {
final trailing = IconButton(
icon: const Icon(Icons.house_outlined),
onPressed: () => AppRoutes.openRoomplan(context),
);
if (lesson.ro.isEmpty) {
return ListTile(
leading: const Icon(Icons.room),
title: const Text('Raum: ?'),
trailing: trailing,
);
}
final entries = lesson.ro.map((r) {
final resolved = LessonResolver.resolveRoom(state, r.id);
final name = firstNonEmpty([resolved.name, r.name, '?']);
final longname = firstNonEmpty([resolved.longName, r.longname, '']);
final building = resolved.building.trim();
return LessonFormatter.formatLine(
name,
longname: longname,
extra: (building.isNotEmpty && building != '?') ? building : null,
);
}).toList();
return _listTile(
icon: Icons.room,
label: lesson.ro.length == 1 ? 'Raum' : 'Räume',
entries: entries,
trailing: trailing,
);
}
static Widget _teacherTile(
BuildContext context,
GetTimetableResponseObject lesson,
) {
final trailing = Visibility(
visible: !kReleaseMode,
child: IconButton(
icon: const Icon(Icons.textsms_outlined),
onPressed: () => UnimplementedDialog.show(context),
),
);
if (lesson.te.isEmpty) {
return ListTile(
leading: const Icon(Icons.person),
title: const Text('Lehrkraft: ?'),
trailing: trailing,
);
}
final entries = lesson.te.map((t) {
final base = LessonFormatter.formatLine(
t.name.isNotEmpty ? t.name : '?',
longname: t.longname,
);
final orgname = (t.orgname ?? '').trim();
return orgname.isEmpty ? base : '$base · ehemals $orgname';
}).toList();
return _listTile(
icon: Icons.person,
label: lesson.te.length == 1 ? 'Lehrkraft' : 'Lehrkräfte',
entries: entries,
trailing: trailing,
);
}
static Widget _listTile({
required IconData icon,
required String label,
required List<String> 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<Widget>(Text.new).toList(),
),
trailing: trailing,
);
}
static List<Widget> _optionalTextTiles(GetTimetableResponseObject lesson) {
return <Widget?>[
_textTile(Icons.info_outline, 'Info', lesson.info),
_textTile(Icons.swap_horiz, 'Vertretungstext', lesson.substText),
_textTile(Icons.subject, 'Stundentext', lesson.lstext),
_textTile(Icons.category_outlined, 'Stundentyp', lesson.lstype),
_textTile(Icons.flag_outlined, 'Statusmerkmale', lesson.statflags),
_textTile(Icons.school_outlined, 'Lerngruppe', lesson.sg),
_textTile(Icons.bookmark_outline, 'Buchungshinweis', lesson.bkRemark),
_textTile(Icons.notes, 'Buchungstext', lesson.bkText),
].whereType<Widget>().toList();
}
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),
);
}
}