migrated timetable integration from WebUntis to the MarianumConnect API, implementing a Dio-based client with bearer token authentication, background session validation, and auto-refresh logic.

This commit is contained in:
2026-05-23 17:32:42 +02:00
parent 2858f910c9
commit 93b9929f8f
106 changed files with 2739 additions and 2624 deletions
+38 -43
View File
@@ -2,31 +2,30 @@ import 'dart:async';
import 'dart:developer';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
import 'package:workmanager/workmanager.dart';
import '../api/marianumconnect/marianumconnect_endpoint.dart';
import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays.dart';
import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms.dart';
import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms_response.dart';
import '../api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects.dart';
import '../api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects_response.dart';
import '../api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid.dart';
import '../api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid_response.dart';
import '../api/marianumconnect/queries/timetable_get_week/timetable_get_week.dart';
import '../api/mhsl/custom_timetable_event/get/get_custom_timetable_event.dart';
import '../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_params.dart';
import '../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
import '../api/webuntis/queries/authenticate/authenticate.dart';
import '../api/webuntis/queries/get_holidays/get_holidays.dart';
import '../api/webuntis/queries/get_holidays/get_holidays_response.dart';
import '../api/webuntis/queries/get_rooms/get_rooms.dart';
import '../api/webuntis/queries/get_rooms/get_rooms_response.dart';
import '../api/webuntis/queries/get_subjects/get_subjects.dart';
import '../api/webuntis/queries/get_subjects/get_subjects_response.dart';
import '../api/webuntis/queries/get_timegrid_units/get_timegrid_units.dart';
import '../api/webuntis/queries/get_timegrid_units/get_timegrid_units_response.dart';
import '../api/webuntis/queries/get_timetable/get_timetable.dart';
import '../api/webuntis/queries/get_timetable/get_timetable_params.dart';
import '../model/account_data.dart';
import '../widget_data/widget_data_mapper.dart';
import '../widget_data/widget_publisher.dart';
import '../widget_data/widget_sync.dart';
/// Periodic widget refresh in a background Dart isolate. Native HTTP would
/// mean reimplementing WebUntis JSON-RPC (auth, session-timeout retry -8520,
/// payload quirks) twice — Dart isolate keeps that logic in one place.
/// Periodic widget refresh in a background Dart isolate. The Marianum-Connect
/// dio singleton + bearer interceptor handle login/refresh transparently —
/// we only need to pin the endpoint to whatever the user picked in the
/// in-app settings before issuing calls.
class WidgetBackgroundTask {
static const String periodicTaskName = 'eu.mhsl.marianum.widget.refresh';
static const String oneOffTaskName = 'eu.mhsl.marianum.widget.refresh.once';
@@ -85,43 +84,39 @@ void _callbackDispatcher() {
Future<void> _refresh() async {
await WidgetSync.ensureInitialized();
await Authenticate.createSession();
// The background isolate doesn't go through main.dart's BlocBuilder, so we
// re-apply the endpoint the foreground last persisted. Without this the
// dio singleton would fall back to its hardcoded live default even when
// the user picked beta/custom in the in-app settings.
final mcBaseUrl = await WidgetSync.getMarianumConnectBaseUrl();
if (mcBaseUrl != null && mcBaseUrl.isNotEmpty) {
MarianumConnectEndpoint.update(mcBaseUrl);
}
final now = WidgetPublisher.widgetNow();
final dateFormat = DateFormat('yyyyMMdd');
// 14-day window so the week-widget rolls forward into next Monday's
// lessons on Friday evening.
final weekStart = _startOfWeek(now);
final weekEndExclusive = weekStart.add(const Duration(days: 14));
final session = await Authenticate.getSession();
final timetable = await GetTimetable(
GetTimetableParams(
options: GetTimetableParamsOptions(
element: GetTimetableParamsOptionsElement(
id: session.personId,
type: session.personType,
keyType: GetTimetableParamsOptionsElementKeyType.id,
),
startDate: int.parse(dateFormat.format(weekStart)),
endDate: int.parse(
dateFormat.format(weekEndExclusive.subtract(const Duration(days: 1))),
),
teacherFields: GetTimetableParamsOptionsFields.all,
subjectFields: GetTimetableParamsOptionsFields.all,
roomFields: GetTimetableParamsOptionsFields.all,
klasseFields: GetTimetableParamsOptionsFields.all,
),
),
).run();
final timetable = await TimetableGetWeek().run(
from: weekStart,
until: weekEndExclusive.subtract(const Duration(days: 1)),
);
// Reference data — failures fall through to null in the mapper rather
// than aborting the whole refresh.
final subjects = await _runOrNull<GetSubjectsResponse>(() => GetSubjects().run());
final rooms = await _runOrNull<GetRoomsResponse>(() => GetRooms().run());
final holidays = await _runOrNull<GetHolidaysResponse>(() => GetHolidays().run());
final timegrid = await _runOrNull<GetTimegridUnitsResponse>(
() => GetTimegridUnits().run(),
final subjects = await _runOrNull<TimetableGetSubjectsResponse>(
() => TimetableGetSubjects().run(),
);
final rooms = await _runOrNull<TimetableGetRoomsResponse>(
() => TimetableGetRooms().run(),
);
final holidays = await _runOrNull<TimetableGetHolidaysResponse>(
() => TimetableGetHolidays().run(),
);
final timegrid = await _runOrNull<TimetableGetTimegridResponse>(
() => TimetableGetTimegrid().run(),
);
final customEvents = await _runOrNull<GetCustomTimetableEventResponse>(
() => GetCustomTimetableEvent(
@@ -129,7 +124,7 @@ Future<void> _refresh() async {
).run(),
);
final lessons = timetable.result;
final lessons = timetable.entries;
final connectDouble = await WidgetSync.getConnectDoubleLessons();
final dayData = WidgetDataMapper.buildDayData(