refactored broad range of the application, split files, modularized calendar and file views, centralized bottom sheets and clipboard handling, and implemented unit test coverage
This commit is contained in:
@@ -1,38 +1,35 @@
|
||||
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 '../../../../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';
|
||||
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 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 =
|
||||
'${Jiffy.parseFromDateTime(appointment.startTime).format(pattern: 'HH:mm')} - '
|
||||
'${Jiffy.parseFromDateTime(appointment.endTime).format(pattern: 'HH:mm')}';
|
||||
final timeRange = appointment.startTime.timeRangeTo(appointment.endTime);
|
||||
|
||||
showAppointmentBottomSheet(
|
||||
showDetailsBottomSheet(
|
||||
context,
|
||||
header: ListTile(
|
||||
leading: Icon(_iconForCode(lesson.code), size: 32),
|
||||
leading: Icon(LessonFormatter.iconForCode(lesson.code), size: 32),
|
||||
title: Text(
|
||||
'${_codePrefix(lesson.code)}$headerTitle',
|
||||
'${LessonFormatter.codePrefix(lesson.code)}$headerTitle',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text(headerLongName.isNotEmpty
|
||||
@@ -43,17 +40,17 @@ class WebuntisLessonSheet {
|
||||
children: (_) => <Widget>[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.notifications_active),
|
||||
title: Text('Status: ${_statusLabel(lesson.code)}'),
|
||||
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 = _resolveSubject(state, s.id);
|
||||
return _formatLine(
|
||||
_firstNonEmpty([resolved.name, s.name, '?']),
|
||||
longname: _firstNonEmpty([resolved.longName, s.longname, '']),
|
||||
final resolved = LessonResolver.resolveSubject(state, s.id);
|
||||
return LessonFormatter.formatLine(
|
||||
firstNonEmpty([resolved.name, s.name, '?']),
|
||||
longname: firstNonEmpty([resolved.longName, s.longname, '']),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
@@ -69,7 +66,7 @@ class WebuntisLessonSheet {
|
||||
icon: Icons.people,
|
||||
label: lesson.kl.length == 1 ? 'Klasse' : 'Klassen',
|
||||
entries: lesson.kl
|
||||
.map((k) => _formatLine(
|
||||
.map((k) => LessonFormatter.formatLine(
|
||||
k.name.isNotEmpty ? k.name : '?',
|
||||
longname: k.longname,
|
||||
))
|
||||
@@ -81,17 +78,6 @@ class WebuntisLessonSheet {
|
||||
);
|
||||
}
|
||||
|
||||
static IconData _iconForCode(String? code) {
|
||||
switch (code) {
|
||||
case 'cancelled':
|
||||
return Icons.event_busy_outlined;
|
||||
case 'irregular':
|
||||
return Icons.swap_horiz;
|
||||
default:
|
||||
return Icons.school_outlined;
|
||||
}
|
||||
}
|
||||
|
||||
static Widget _roomTile(BuildContext context, TimetableState state, GetTimetableResponseObject lesson) {
|
||||
final trailing = IconButton(
|
||||
icon: const Icon(Icons.house_outlined),
|
||||
@@ -107,11 +93,11 @@ class WebuntisLessonSheet {
|
||||
}
|
||||
|
||||
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 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 _formatLine(
|
||||
return LessonFormatter.formatLine(
|
||||
name,
|
||||
longname: longname,
|
||||
extra: (building.isNotEmpty && building != '?') ? building : null,
|
||||
@@ -144,7 +130,7 @@ class WebuntisLessonSheet {
|
||||
}
|
||||
|
||||
final entries = lesson.te.map((t) {
|
||||
final base = _formatLine(
|
||||
final base = LessonFormatter.formatLine(
|
||||
t.name.isNotEmpty ? t.name : '?',
|
||||
longname: t.longname,
|
||||
);
|
||||
@@ -206,54 +192,4 @@ class WebuntisLessonSheet {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user