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
@@ -0,0 +1,33 @@
import 'package:dio/dio.dart';
import '../../errors/marianumconnect_error.dart';
import '../../marianumconnect_api.dart';
import '../../marianumconnect_endpoint.dart';
import 'timetable_get_week_response.dart';
class TimetableGetWeek {
final Dio _dio;
TimetableGetWeek({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
Future<TimetableGetWeekResponse> run({
required DateTime from,
required DateTime until,
}) async {
try {
final response = await _dio.get<Map<String, dynamic>>(
MarianumConnectEndpoint.resolve('timetable/me'),
queryParameters: {
'from': _format(from),
'until': _format(until),
},
);
return TimetableGetWeekResponse.fromJson(response.data!);
} on DioException catch (e) {
throw mapMarianumConnectError(e);
}
}
String _format(DateTime d) =>
'${d.year.toString().padLeft(4, '0')}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';
}
@@ -0,0 +1,108 @@
import 'package:json_annotation/json_annotation.dart';
import '../../../api_response.dart';
part 'timetable_get_week_response.g.dart';
@JsonSerializable(explicitToJson: true)
class McTimetableTeacher {
final String shortName;
final String displayName;
final String? originalShortName;
final String? originalDisplayName;
McTimetableTeacher({
required this.shortName,
required this.displayName,
this.originalShortName,
this.originalDisplayName,
});
factory McTimetableTeacher.fromJson(Map<String, dynamic> json) =>
_$McTimetableTeacherFromJson(json);
Map<String, dynamic> toJson() => _$McTimetableTeacherToJson(this);
}
@JsonSerializable(explicitToJson: true)
class McTimetableEntry {
final int id;
@JsonKey(fromJson: _dateFromJson, toJson: _dateToJson)
final DateTime date;
@JsonKey(fromJson: _timeFromJson, toJson: _timeToJson)
final DateTime startTime;
@JsonKey(fromJson: _timeFromJson, toJson: _timeToJson)
final DateTime endTime;
final List<String> subjects;
final List<McTimetableTeacher> teachers;
final List<String> rooms;
final List<String> classNames;
final String lessonType;
final String status;
final String? substitutionText;
final String? lessonText;
final String? infoText;
McTimetableEntry({
required this.id,
required this.date,
required this.startTime,
required this.endTime,
required this.subjects,
required this.teachers,
required this.rooms,
required this.classNames,
required this.lessonType,
required this.status,
required this.substitutionText,
required this.lessonText,
required this.infoText,
});
factory McTimetableEntry.fromJson(Map<String, dynamic> json) =>
_$McTimetableEntryFromJson(json);
Map<String, dynamic> toJson() => _$McTimetableEntryToJson(this);
/// Combines the calendar date with the hour/minute portion of [startTime]
/// (which carries a 1970 placeholder date) into a real DateTime.
DateTime get startDateTime =>
DateTime(date.year, date.month, date.day, startTime.hour, startTime.minute);
DateTime get endDateTime =>
DateTime(date.year, date.month, date.day, endTime.hour, endTime.minute);
static DateTime _dateFromJson(String raw) => DateTime.parse(raw);
static String _dateToJson(DateTime d) =>
'${d.year.toString().padLeft(4, '0')}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';
// Backend sends ISO_LOCAL_TIME (e.g. "08:00:00" or "08:00"). Parsed via a
// fixed-date prefix so we get a real DateTime out of it; only hour/minute
// are meaningful for rendering.
static DateTime _timeFromJson(String raw) => DateTime.parse('1970-01-01T$raw');
static String _timeToJson(DateTime t) =>
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}:${t.second.toString().padLeft(2, '0')}';
}
@JsonSerializable(explicitToJson: true)
class TimetableGetWeekResponse extends ApiResponse {
@JsonKey(fromJson: McTimetableEntry._dateFromJson, toJson: McTimetableEntry._dateToJson)
final DateTime from;
@JsonKey(fromJson: McTimetableEntry._dateFromJson, toJson: McTimetableEntry._dateToJson)
final DateTime until;
final List<McTimetableEntry> entries;
TimetableGetWeekResponse({
required this.from,
required this.until,
required this.entries,
});
factory TimetableGetWeekResponse.fromJson(Map<String, dynamic> json) =>
_$TimetableGetWeekResponseFromJson(json);
Map<String, dynamic> toJson() => _$TimetableGetWeekResponseToJson(this);
}
@@ -0,0 +1,86 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'timetable_get_week_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
McTimetableTeacher _$McTimetableTeacherFromJson(Map<String, dynamic> json) =>
McTimetableTeacher(
shortName: json['shortName'] as String,
displayName: json['displayName'] as String,
originalShortName: json['originalShortName'] as String?,
originalDisplayName: json['originalDisplayName'] as String?,
);
Map<String, dynamic> _$McTimetableTeacherToJson(McTimetableTeacher instance) =>
<String, dynamic>{
'shortName': instance.shortName,
'displayName': instance.displayName,
'originalShortName': instance.originalShortName,
'originalDisplayName': instance.originalDisplayName,
};
McTimetableEntry _$McTimetableEntryFromJson(Map<String, dynamic> json) =>
McTimetableEntry(
id: (json['id'] as num).toInt(),
date: McTimetableEntry._dateFromJson(json['date'] as String),
startTime: McTimetableEntry._timeFromJson(json['startTime'] as String),
endTime: McTimetableEntry._timeFromJson(json['endTime'] as String),
subjects: (json['subjects'] as List<dynamic>)
.map((e) => e as String)
.toList(),
teachers: (json['teachers'] as List<dynamic>)
.map((e) => McTimetableTeacher.fromJson(e as Map<String, dynamic>))
.toList(),
rooms: (json['rooms'] as List<dynamic>).map((e) => e as String).toList(),
classNames: (json['classNames'] as List<dynamic>)
.map((e) => e as String)
.toList(),
lessonType: json['lessonType'] as String,
status: json['status'] as String,
substitutionText: json['substitutionText'] as String?,
lessonText: json['lessonText'] as String?,
infoText: json['infoText'] as String?,
);
Map<String, dynamic> _$McTimetableEntryToJson(McTimetableEntry instance) =>
<String, dynamic>{
'id': instance.id,
'date': McTimetableEntry._dateToJson(instance.date),
'startTime': McTimetableEntry._timeToJson(instance.startTime),
'endTime': McTimetableEntry._timeToJson(instance.endTime),
'subjects': instance.subjects,
'teachers': instance.teachers.map((e) => e.toJson()).toList(),
'rooms': instance.rooms,
'classNames': instance.classNames,
'lessonType': instance.lessonType,
'status': instance.status,
'substitutionText': instance.substitutionText,
'lessonText': instance.lessonText,
'infoText': instance.infoText,
};
TimetableGetWeekResponse _$TimetableGetWeekResponseFromJson(
Map<String, dynamic> json,
) =>
TimetableGetWeekResponse(
from: McTimetableEntry._dateFromJson(json['from'] as String),
until: McTimetableEntry._dateFromJson(json['until'] as String),
entries: (json['entries'] as List<dynamic>)
.map((e) => McTimetableEntry.fromJson(e as Map<String, dynamic>))
.toList(),
)
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$TimetableGetWeekResponseToJson(
TimetableGetWeekResponse instance,
) => <String, dynamic>{
'headers': ?instance.headers,
'from': McTimetableEntry._dateToJson(instance.from),
'until': McTimetableEntry._dateToJson(instance.until),
'entries': instance.entries.map((e) => e.toJson()).toList(),
};