254 lines
8.4 KiB
Dart
254 lines
8.4 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:jiffy/jiffy.dart';
|
|
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
|
|
|
import '../../../../api/webuntis/queries/get_rooms/get_rooms_response.dart';
|
|
import '../../../../api/webuntis/queries/get_subjects/get_subjects_response.dart';
|
|
import '../../../../api/webuntis/queries/get_timetable/get_timetable_response.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/unimplemented_dialog.dart';
|
|
import 'bottom_sheet.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 = _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 : '';
|
|
|
|
showAppointmentBottomSheet(
|
|
context,
|
|
header: (_) => Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'${_codePrefix(lesson.code)}$headerTitle',
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(fontSize: 25),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
if (headerLongName.isNotEmpty) Text(headerLongName),
|
|
Text(
|
|
'${Jiffy.parseFromDateTime(appointment.startTime).format(pattern: 'HH:mm')} - '
|
|
'${Jiffy.parseFromDateTime(appointment.endTime).format(pattern: 'HH:mm')}',
|
|
style: const TextStyle(fontSize: 15),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
body: (_) => SliverChildListDelegate(<Widget>[
|
|
const Divider(),
|
|
ListTile(
|
|
leading: const Icon(Icons.notifications_active),
|
|
title: Text('Status: ${_statusLabel(lesson.code)}'),
|
|
),
|
|
if (lesson.su.length > 1)
|
|
_listTile(
|
|
icon: Icons.book_outlined,
|
|
label: 'Fächer',
|
|
entries: lesson.su.map((s) {
|
|
final resolved = _resolveSubject(state, s.id);
|
|
return _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) => _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 = _resolveRoom(state, r.id);
|
|
final name = _firstNonEmpty([resolved.name, r.name, '?']);
|
|
final longname = _firstNonEmpty([resolved.longName, r.longname, '']);
|
|
final building = resolved.building.trim();
|
|
return _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 = _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) return null;
|
|
return ListTile(
|
|
leading: Icon(icon),
|
|
title: Text(label),
|
|
subtitle: Text(text),
|
|
);
|
|
}
|
|
|
|
static String _formatLine(String name, {String? longname, String? extra}) {
|
|
final parts = <String>[
|
|
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 _firstNonEmpty(List<String> values) {
|
|
for (final v in values) {
|
|
if (v.trim().isNotEmpty) return v;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
static String _statusLabel(String? code) {
|
|
switch (code) {
|
|
case null:
|
|
case '':
|
|
return 'Regulär';
|
|
case 'cancelled':
|
|
return 'Entfällt';
|
|
case 'irregular':
|
|
return 'Geändert';
|
|
default:
|
|
return code;
|
|
}
|
|
}
|
|
|
|
static String _codePrefix(String? code) {
|
|
if (code == 'cancelled') return 'Entfällt: ';
|
|
if (code == 'irregular') return 'Änderung: ';
|
|
return code ?? '';
|
|
}
|
|
|
|
static GetSubjectsResponseObject _resolveSubject(TimetableState state, int? id) {
|
|
final fallback = GetSubjectsResponseObject(0, '?', 'Unbekannt', '?', true);
|
|
if (id == null) return fallback;
|
|
return state.subjects?.result.firstWhereOrNull((s) => s.id == id) ?? fallback;
|
|
}
|
|
|
|
static GetRoomsResponseObject _resolveRoom(TimetableState state, int? id) {
|
|
final fallback = GetRoomsResponseObject(0, '?', 'Unbekannt', true, '');
|
|
if (id == null) return fallback;
|
|
return state.rooms?.result.firstWhereOrNull((r) => r.id == id) ?? fallback;
|
|
}
|
|
}
|