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
@@ -2,9 +2,8 @@ import 'dart:developer';
import 'package:intl/intl.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
import '../../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
import '../../../../../api/webuntis/queries/get_timetable/get_timetable_response.dart';
import '../../../../../api/webuntis/webuntis_error.dart';
import '../../../../../extensions/date_time.dart';
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc.dart';
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc_event.dart';
@@ -46,12 +45,8 @@ class TimetableBloc
}
/// Persisted state may carry a stale `startDate`/`endDate` from the user's
/// last view as well as `accessibleStartDate`/`accessibleEndDate` learned
/// from `-7004 no allowed date` errors during scroll. Both must reset on
/// every cold start: otherwise the calendar can mount on a months-old week
/// (e.g. last December's Christmas holidays) or get permanently clamped
/// inside a window Webuntis once refused — even though the server would
/// happily serve the user's current week now.
/// last view. Reset on every cold start so the calendar always mounts on
/// the current week, not on whatever week the user closed the app on.
@override
TimetableState fromStorage(Map<String, dynamic> json) {
final stored = TimetableState.fromJson(json);
@@ -142,55 +137,12 @@ class TimetableBloc
);
if (_lastWeekRequestStart.isAfter(requestStart)) return;
_writeWeekToCache(startDate, week);
} on WebuntisError catch (e) {
if (e.code == _outOfRangeErrorCode) {
_narrowAccessibleRange(startDate, endDate);
// Out-of-range is expected when the user scrolls into territory
// Webuntis doesn't grant access to — surface to UI as a normal
// empty week instead of letting the loadable state escalate it
// into a red error screen.
return;
}
log(
'Webuntis getWeek error: code=${e.code} message="${e.message}" '
'for $startDate$endDate',
);
onError?.call(e);
} catch (e) {
log('getWeek error for $startDate$endDate: $e');
onError?.call(e);
}
}
/// Webuntis returns this for weeks the user has no access to (typically
/// before the active enrolment / after a teacher's planning window).
static const int _outOfRangeErrorCode = -7004;
/// Pulls the calendar's permitted scroll range inward based on a denied
/// week. We don't know the exact cutoff — only that *this* week is out
/// of reach — so we always pick the tighter of the existing bound and
/// the newly discovered one. Pre-now denials shrink the lower bound,
/// post-now denials the upper.
void _narrowAccessibleRange(DateTime startDate, DateTime endDate) {
final now = DateTime.now();
final isPast = endDate.isBefore(now);
add(
Emit((s) {
if (isPast) {
final candidate = endDate.addDays(1);
final current = s.accessibleStartDate;
if (current != null && !candidate.isAfter(current)) return s;
return s.copyWith(accessibleStartDate: candidate);
}
// Treat anything not strictly past as a forward-direction denial,
// including the rare case where startDate == now.
final candidate = startDate.subtractDays(1);
final current = s.accessibleEndDate;
if (current != null && !candidate.isBefore(current)) return s;
return s.copyWith(accessibleEndDate: candidate);
}),
);
}
Future<void> _loadStaticReferenceData({
void Function(Object)? onError,
bool renew = false,
@@ -271,11 +223,11 @@ class TimetableBloc
.catchError((_) {});
}
void _writeWeekToCache(DateTime weekStart, GetTimetableResponse week) {
void _writeWeekToCache(DateTime weekStart, TimetableGetWeekResponse week) {
final key = _weekKeyFormat.format(weekStart);
add(
Emit((s) {
final updated = Map<String, GetTimetableResponse>.of(s.weekCache);
final updated = Map<String, TimetableGetWeekResponse>.of(s.weekCache);
updated[key] = week;
return s.copyWith(weekCache: updated, dataVersion: s.dataVersion + 1);
}),
@@ -1,12 +1,12 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms_response.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_schoolyear/timetable_get_schoolyear_response.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects_response.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid_response.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
import '../../../../../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
import '../../../../../api/webuntis/queries/get_current_schoolyear/get_current_schoolyear_response.dart';
import '../../../../../api/webuntis/queries/get_holidays/get_holidays_response.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_timegrid_units/get_timegrid_units_response.dart';
import '../../../../../api/webuntis/queries/get_timetable/get_timetable_response.dart';
part 'timetable_state.freezed.dart';
part 'timetable_state.g.dart';
@@ -16,18 +16,18 @@ abstract class TimetableState with _$TimetableState {
const TimetableState._();
const factory TimetableState({
@Default(<String, GetTimetableResponse>{})
Map<String, GetTimetableResponse> weekCache,
GetRoomsResponse? rooms,
GetSubjectsResponse? subjects,
GetHolidaysResponse? schoolHolidays,
GetCurrentSchoolyearResponse? schoolyear,
GetTimegridUnitsResponse? timegrid,
@Default(<String, TimetableGetWeekResponse>{})
Map<String, TimetableGetWeekResponse> weekCache,
TimetableGetRoomsResponse? rooms,
TimetableGetSubjectsResponse? subjects,
TimetableGetHolidaysResponse? schoolHolidays,
TimetableGetSchoolyearResponse? schoolyear,
TimetableGetTimegridResponse? timegrid,
GetCustomTimetableEventResponse? customEvents,
required DateTime startDate,
required DateTime endDate,
@Default(0) int dataVersion,
// Boundaries learned from `-7004 no allowed date` errors during scroll.
// Boundaries learned from past server denials of inaccessible weeks.
// Inclusive: weeks whose start is on/before `accessibleEndDate` and
// whose end is on/after `accessibleStartDate` are within the user's
// permitted range. Null = no upper / lower bound discovered yet.
@@ -38,8 +38,8 @@ abstract class TimetableState with _$TimetableState {
factory TimetableState.fromJson(Map<String, Object?> json) =>
_$TimetableStateFromJson(json);
Iterable<GetTimetableResponseObject> getAllKnownLessons() =>
weekCache.values.expand((response) => response.result);
Iterable<McTimetableEntry> getAllKnownLessons() =>
weekCache.values.expand((response) => response.entries);
bool get hasReferenceData =>
rooms != null &&
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$TimetableState {
Map<String, GetTimetableResponse> get weekCache; GetRoomsResponse? get rooms; GetSubjectsResponse? get subjects; GetHolidaysResponse? get schoolHolidays; GetCurrentSchoolyearResponse? get schoolyear; GetTimegridUnitsResponse? get timegrid; GetCustomTimetableEventResponse? get customEvents; DateTime get startDate; DateTime get endDate; int get dataVersion;// Boundaries learned from `-7004 no allowed date` errors during scroll.
Map<String, TimetableGetWeekResponse> get weekCache; TimetableGetRoomsResponse? get rooms; TimetableGetSubjectsResponse? get subjects; TimetableGetHolidaysResponse? get schoolHolidays; TimetableGetSchoolyearResponse? get schoolyear; TimetableGetTimegridResponse? get timegrid; GetCustomTimetableEventResponse? get customEvents; DateTime get startDate; DateTime get endDate; int get dataVersion;// Boundaries learned from past server denials of inaccessible weeks.
// Inclusive: weeks whose start is on/before `accessibleEndDate` and
// whose end is on/after `accessibleStartDate` are within the user's
// permitted range. Null = no upper / lower bound discovered yet.
@@ -52,7 +52,7 @@ abstract mixin class $TimetableStateCopyWith<$Res> {
factory $TimetableStateCopyWith(TimetableState value, $Res Function(TimetableState) _then) = _$TimetableStateCopyWithImpl;
@useResult
$Res call({
Map<String, GetTimetableResponse> weekCache, GetRoomsResponse? rooms, GetSubjectsResponse? subjects, GetHolidaysResponse? schoolHolidays, GetCurrentSchoolyearResponse? schoolyear, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate
Map<String, TimetableGetWeekResponse> weekCache, TimetableGetRoomsResponse? rooms, TimetableGetSubjectsResponse? subjects, TimetableGetHolidaysResponse? schoolHolidays, TimetableGetSchoolyearResponse? schoolyear, TimetableGetTimegridResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate
});
@@ -72,12 +72,12 @@ class _$TimetableStateCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @override $Res call({Object? weekCache = null,Object? rooms = freezed,Object? subjects = freezed,Object? schoolHolidays = freezed,Object? schoolyear = freezed,Object? timegrid = freezed,Object? customEvents = freezed,Object? startDate = null,Object? endDate = null,Object? dataVersion = null,Object? accessibleStartDate = freezed,Object? accessibleEndDate = freezed,}) {
return _then(_self.copyWith(
weekCache: null == weekCache ? _self.weekCache : weekCache // ignore: cast_nullable_to_non_nullable
as Map<String, GetTimetableResponse>,rooms: freezed == rooms ? _self.rooms : rooms // ignore: cast_nullable_to_non_nullable
as GetRoomsResponse?,subjects: freezed == subjects ? _self.subjects : subjects // ignore: cast_nullable_to_non_nullable
as GetSubjectsResponse?,schoolHolidays: freezed == schoolHolidays ? _self.schoolHolidays : schoolHolidays // ignore: cast_nullable_to_non_nullable
as GetHolidaysResponse?,schoolyear: freezed == schoolyear ? _self.schoolyear : schoolyear // ignore: cast_nullable_to_non_nullable
as GetCurrentSchoolyearResponse?,timegrid: freezed == timegrid ? _self.timegrid : timegrid // ignore: cast_nullable_to_non_nullable
as GetTimegridUnitsResponse?,customEvents: freezed == customEvents ? _self.customEvents : customEvents // ignore: cast_nullable_to_non_nullable
as Map<String, TimetableGetWeekResponse>,rooms: freezed == rooms ? _self.rooms : rooms // ignore: cast_nullable_to_non_nullable
as TimetableGetRoomsResponse?,subjects: freezed == subjects ? _self.subjects : subjects // ignore: cast_nullable_to_non_nullable
as TimetableGetSubjectsResponse?,schoolHolidays: freezed == schoolHolidays ? _self.schoolHolidays : schoolHolidays // ignore: cast_nullable_to_non_nullable
as TimetableGetHolidaysResponse?,schoolyear: freezed == schoolyear ? _self.schoolyear : schoolyear // ignore: cast_nullable_to_non_nullable
as TimetableGetSchoolyearResponse?,timegrid: freezed == timegrid ? _self.timegrid : timegrid // ignore: cast_nullable_to_non_nullable
as TimetableGetTimegridResponse?,customEvents: freezed == customEvents ? _self.customEvents : customEvents // ignore: cast_nullable_to_non_nullable
as GetCustomTimetableEventResponse?,startDate: null == startDate ? _self.startDate : startDate // ignore: cast_nullable_to_non_nullable
as DateTime,endDate: null == endDate ? _self.endDate : endDate // ignore: cast_nullable_to_non_nullable
as DateTime,dataVersion: null == dataVersion ? _self.dataVersion : dataVersion // ignore: cast_nullable_to_non_nullable
@@ -168,7 +168,7 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Map<String, GetTimetableResponse> weekCache, GetRoomsResponse? rooms, GetSubjectsResponse? subjects, GetHolidaysResponse? schoolHolidays, GetCurrentSchoolyearResponse? schoolyear, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Map<String, TimetableGetWeekResponse> weekCache, TimetableGetRoomsResponse? rooms, TimetableGetSubjectsResponse? subjects, TimetableGetHolidaysResponse? schoolHolidays, TimetableGetSchoolyearResponse? schoolyear, TimetableGetTimegridResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _TimetableState() when $default != null:
return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,_that.schoolyear,_that.timegrid,_that.customEvents,_that.startDate,_that.endDate,_that.dataVersion,_that.accessibleStartDate,_that.accessibleEndDate);case _:
@@ -189,7 +189,7 @@ return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Map<String, GetTimetableResponse> weekCache, GetRoomsResponse? rooms, GetSubjectsResponse? subjects, GetHolidaysResponse? schoolHolidays, GetCurrentSchoolyearResponse? schoolyear, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Map<String, TimetableGetWeekResponse> weekCache, TimetableGetRoomsResponse? rooms, TimetableGetSubjectsResponse? subjects, TimetableGetHolidaysResponse? schoolHolidays, TimetableGetSchoolyearResponse? schoolyear, TimetableGetTimegridResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate) $default,) {final _that = this;
switch (_that) {
case _TimetableState():
return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,_that.schoolyear,_that.timegrid,_that.customEvents,_that.startDate,_that.endDate,_that.dataVersion,_that.accessibleStartDate,_that.accessibleEndDate);case _:
@@ -209,7 +209,7 @@ return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Map<String, GetTimetableResponse> weekCache, GetRoomsResponse? rooms, GetSubjectsResponse? subjects, GetHolidaysResponse? schoolHolidays, GetCurrentSchoolyearResponse? schoolyear, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Map<String, TimetableGetWeekResponse> weekCache, TimetableGetRoomsResponse? rooms, TimetableGetSubjectsResponse? subjects, TimetableGetHolidaysResponse? schoolHolidays, TimetableGetSchoolyearResponse? schoolyear, TimetableGetTimegridResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate)? $default,) {final _that = this;
switch (_that) {
case _TimetableState() when $default != null:
return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,_that.schoolyear,_that.timegrid,_that.customEvents,_that.startDate,_that.endDate,_that.dataVersion,_that.accessibleStartDate,_that.accessibleEndDate);case _:
@@ -224,26 +224,26 @@ return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,
@JsonSerializable()
class _TimetableState extends TimetableState {
const _TimetableState({final Map<String, GetTimetableResponse> weekCache = const <String, GetTimetableResponse>{}, this.rooms, this.subjects, this.schoolHolidays, this.schoolyear, this.timegrid, this.customEvents, required this.startDate, required this.endDate, this.dataVersion = 0, this.accessibleStartDate, this.accessibleEndDate}): _weekCache = weekCache,super._();
const _TimetableState({final Map<String, TimetableGetWeekResponse> weekCache = const <String, TimetableGetWeekResponse>{}, this.rooms, this.subjects, this.schoolHolidays, this.schoolyear, this.timegrid, this.customEvents, required this.startDate, required this.endDate, this.dataVersion = 0, this.accessibleStartDate, this.accessibleEndDate}): _weekCache = weekCache,super._();
factory _TimetableState.fromJson(Map<String, dynamic> json) => _$TimetableStateFromJson(json);
final Map<String, GetTimetableResponse> _weekCache;
@override@JsonKey() Map<String, GetTimetableResponse> get weekCache {
final Map<String, TimetableGetWeekResponse> _weekCache;
@override@JsonKey() Map<String, TimetableGetWeekResponse> get weekCache {
if (_weekCache is EqualUnmodifiableMapView) return _weekCache;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_weekCache);
}
@override final GetRoomsResponse? rooms;
@override final GetSubjectsResponse? subjects;
@override final GetHolidaysResponse? schoolHolidays;
@override final GetCurrentSchoolyearResponse? schoolyear;
@override final GetTimegridUnitsResponse? timegrid;
@override final TimetableGetRoomsResponse? rooms;
@override final TimetableGetSubjectsResponse? subjects;
@override final TimetableGetHolidaysResponse? schoolHolidays;
@override final TimetableGetSchoolyearResponse? schoolyear;
@override final TimetableGetTimegridResponse? timegrid;
@override final GetCustomTimetableEventResponse? customEvents;
@override final DateTime startDate;
@override final DateTime endDate;
@override@JsonKey() final int dataVersion;
// Boundaries learned from `-7004 no allowed date` errors during scroll.
// Boundaries learned from past server denials of inaccessible weeks.
// Inclusive: weeks whose start is on/before `accessibleEndDate` and
// whose end is on/after `accessibleStartDate` are within the user's
// permitted range. Null = no upper / lower bound discovered yet.
@@ -283,7 +283,7 @@ abstract mixin class _$TimetableStateCopyWith<$Res> implements $TimetableStateCo
factory _$TimetableStateCopyWith(_TimetableState value, $Res Function(_TimetableState) _then) = __$TimetableStateCopyWithImpl;
@override @useResult
$Res call({
Map<String, GetTimetableResponse> weekCache, GetRoomsResponse? rooms, GetSubjectsResponse? subjects, GetHolidaysResponse? schoolHolidays, GetCurrentSchoolyearResponse? schoolyear, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate
Map<String, TimetableGetWeekResponse> weekCache, TimetableGetRoomsResponse? rooms, TimetableGetSubjectsResponse? subjects, TimetableGetHolidaysResponse? schoolHolidays, TimetableGetSchoolyearResponse? schoolyear, TimetableGetTimegridResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion, DateTime? accessibleStartDate, DateTime? accessibleEndDate
});
@@ -303,12 +303,12 @@ class __$TimetableStateCopyWithImpl<$Res>
@override @pragma('vm:prefer-inline') $Res call({Object? weekCache = null,Object? rooms = freezed,Object? subjects = freezed,Object? schoolHolidays = freezed,Object? schoolyear = freezed,Object? timegrid = freezed,Object? customEvents = freezed,Object? startDate = null,Object? endDate = null,Object? dataVersion = null,Object? accessibleStartDate = freezed,Object? accessibleEndDate = freezed,}) {
return _then(_TimetableState(
weekCache: null == weekCache ? _self._weekCache : weekCache // ignore: cast_nullable_to_non_nullable
as Map<String, GetTimetableResponse>,rooms: freezed == rooms ? _self.rooms : rooms // ignore: cast_nullable_to_non_nullable
as GetRoomsResponse?,subjects: freezed == subjects ? _self.subjects : subjects // ignore: cast_nullable_to_non_nullable
as GetSubjectsResponse?,schoolHolidays: freezed == schoolHolidays ? _self.schoolHolidays : schoolHolidays // ignore: cast_nullable_to_non_nullable
as GetHolidaysResponse?,schoolyear: freezed == schoolyear ? _self.schoolyear : schoolyear // ignore: cast_nullable_to_non_nullable
as GetCurrentSchoolyearResponse?,timegrid: freezed == timegrid ? _self.timegrid : timegrid // ignore: cast_nullable_to_non_nullable
as GetTimegridUnitsResponse?,customEvents: freezed == customEvents ? _self.customEvents : customEvents // ignore: cast_nullable_to_non_nullable
as Map<String, TimetableGetWeekResponse>,rooms: freezed == rooms ? _self.rooms : rooms // ignore: cast_nullable_to_non_nullable
as TimetableGetRoomsResponse?,subjects: freezed == subjects ? _self.subjects : subjects // ignore: cast_nullable_to_non_nullable
as TimetableGetSubjectsResponse?,schoolHolidays: freezed == schoolHolidays ? _self.schoolHolidays : schoolHolidays // ignore: cast_nullable_to_non_nullable
as TimetableGetHolidaysResponse?,schoolyear: freezed == schoolyear ? _self.schoolyear : schoolyear // ignore: cast_nullable_to_non_nullable
as TimetableGetSchoolyearResponse?,timegrid: freezed == timegrid ? _self.timegrid : timegrid // ignore: cast_nullable_to_non_nullable
as TimetableGetTimegridResponse?,customEvents: freezed == customEvents ? _self.customEvents : customEvents // ignore: cast_nullable_to_non_nullable
as GetCustomTimetableEventResponse?,startDate: null == startDate ? _self.startDate : startDate // ignore: cast_nullable_to_non_nullable
as DateTime,endDate: null == endDate ? _self.endDate : endDate // ignore: cast_nullable_to_non_nullable
as DateTime,dataVersion: null == dataVersion ? _self.dataVersion : dataVersion // ignore: cast_nullable_to_non_nullable
@@ -12,31 +12,33 @@ _TimetableState _$TimetableStateFromJson(Map<String, dynamic> json) =>
(json['weekCache'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(
k,
GetTimetableResponse.fromJson(e as Map<String, dynamic>),
TimetableGetWeekResponse.fromJson(e as Map<String, dynamic>),
),
) ??
const <String, GetTimetableResponse>{},
const <String, TimetableGetWeekResponse>{},
rooms: json['rooms'] == null
? null
: GetRoomsResponse.fromJson(json['rooms'] as Map<String, dynamic>),
: TimetableGetRoomsResponse.fromJson(
json['rooms'] as Map<String, dynamic>,
),
subjects: json['subjects'] == null
? null
: GetSubjectsResponse.fromJson(
: TimetableGetSubjectsResponse.fromJson(
json['subjects'] as Map<String, dynamic>,
),
schoolHolidays: json['schoolHolidays'] == null
? null
: GetHolidaysResponse.fromJson(
: TimetableGetHolidaysResponse.fromJson(
json['schoolHolidays'] as Map<String, dynamic>,
),
schoolyear: json['schoolyear'] == null
? null
: GetCurrentSchoolyearResponse.fromJson(
: TimetableGetSchoolyearResponse.fromJson(
json['schoolyear'] as Map<String, dynamic>,
),
timegrid: json['timegrid'] == null
? null
: GetTimegridUnitsResponse.fromJson(
: TimetableGetTimegridResponse.fromJson(
json['timegrid'] as Map<String, dynamic>,
),
customEvents: json['customEvents'] == null
@@ -1,5 +1,15 @@
import 'package:intl/intl.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_schoolyear/timetable_get_schoolyear.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_schoolyear/timetable_get_schoolyear_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/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
import '../../../../../api/mhsl/custom_timetable_event/add/add_custom_timetable_event.dart';
import '../../../../../api/mhsl/custom_timetable_event/add/add_custom_timetable_event_params.dart';
import '../../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
@@ -11,89 +21,77 @@ import '../../../../../api/mhsl/custom_timetable_event/remove/remove_custom_time
import '../../../../../api/mhsl/custom_timetable_event/update/update_custom_timetable_event.dart';
import '../../../../../api/mhsl/custom_timetable_event/update/update_custom_timetable_event_params.dart';
import '../../../../../api/request_cache.dart';
import '../../../../../api/webuntis/queries/get_current_schoolyear/get_current_schoolyear_cache.dart';
import '../../../../../api/webuntis/queries/get_current_schoolyear/get_current_schoolyear_response.dart';
import '../../../../../api/webuntis/queries/get_holidays/get_holidays_cache.dart';
import '../../../../../api/webuntis/queries/get_holidays/get_holidays_response.dart';
import '../../../../../api/webuntis/queries/get_rooms/get_rooms_cache.dart';
import '../../../../../api/webuntis/queries/get_rooms/get_rooms_response.dart';
import '../../../../../api/webuntis/queries/get_subjects/get_subjects_cache.dart';
import '../../../../../api/webuntis/queries/get_subjects/get_subjects_response.dart';
import '../../../../../api/webuntis/queries/get_timegrid_units/get_timegrid_units_cache.dart';
import '../../../../../api/webuntis/queries/get_timegrid_units/get_timegrid_units_response.dart';
import '../../../../../api/webuntis/queries/get_timetable/get_timetable_cache.dart';
import '../../../../../api/webuntis/queries/get_timetable/get_timetable_response.dart';
import '../../../../../model/account_data.dart';
/// Pulls the timetable from the Marianum-Connect mobile API. Each MC endpoint
/// is its own HTTP call; this provider just exposes the lazy futures so the
/// bloc can chain them without seeing the dio layer. Custom events still come
/// from the MHSL backend and are unchanged.
class TimetableDataProvider {
static final DateFormat _dateFormat = DateFormat('yyyyMMdd');
Future<GetTimetableResponse> getWeek(
Future<TimetableGetWeekResponse> getWeek(
DateTime startDate,
DateTime endDate, {
void Function(Object)? onError,
bool renew = false,
}) => resolveFromCache<GetTimetableResponse>(
(onUpdate, onError) => GetTimetableCache(
startdate: int.parse(_dateFormat.format(startDate)),
enddate: int.parse(_dateFormat.format(endDate)),
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getWeek',
);
}) async {
try {
return await TimetableGetWeek().run(from: startDate, until: endDate);
} catch (e) {
onError?.call(e);
rethrow;
}
}
Future<GetRoomsResponse> getRooms({
Future<TimetableGetRoomsResponse> getRooms({
void Function(Object)? onError,
bool renew = false,
}) => resolveFromCache<GetRoomsResponse>(
(onUpdate, onError) =>
GetRoomsCache(renew: renew, onUpdate: onUpdate, onError: onError),
onError: onError,
operationName: 'getRooms',
);
}) async {
try {
return await TimetableGetRooms().run();
} catch (e) {
onError?.call(e);
rethrow;
}
}
Future<GetSubjectsResponse> getSubjects({
Future<TimetableGetSubjectsResponse> getSubjects({
void Function(Object)? onError,
bool renew = false,
}) => resolveFromCache<GetSubjectsResponse>(
(onUpdate, onError) =>
GetSubjectsCache(renew: renew, onUpdate: onUpdate, onError: onError),
onError: onError,
operationName: 'getSubjects',
);
}) async {
try {
return await TimetableGetSubjects().run();
} catch (e) {
onError?.call(e);
rethrow;
}
}
Future<GetHolidaysResponse> getSchoolHolidays({
Future<TimetableGetHolidaysResponse> getSchoolHolidays({
void Function(Object)? onError,
bool renew = false,
}) => resolveFromCache<GetHolidaysResponse>(
(onUpdate, onError) =>
GetHolidaysCache(renew: renew, onUpdate: onUpdate, onError: onError),
onError: onError,
operationName: 'getSchoolHolidays',
);
}) async {
try {
return await TimetableGetHolidays().run();
} catch (e) {
onError?.call(e);
rethrow;
}
}
Future<GetCurrentSchoolyearResponse> getCurrentSchoolyear({
Future<TimetableGetSchoolyearResponse> getCurrentSchoolyear({
void Function(Object)? onError,
bool renew = false,
}) => resolveFromCache<GetCurrentSchoolyearResponse>(
(onUpdate, onError) => GetCurrentSchoolyearCache(
renew: renew,
onUpdate: onUpdate,
onError: onError,
),
onError: onError,
operationName: 'getCurrentSchoolyear',
);
}) async {
try {
return await TimetableGetSchoolyear().run();
} catch (e) {
onError?.call(e);
rethrow;
}
}
Future<GetTimegridUnitsResponse> getTimegrid({bool renew = false}) =>
resolveFromCache<GetTimegridUnitsResponse>(
(onUpdate, _) =>
GetTimegridUnitsCache(renew: renew, onUpdate: onUpdate),
operationName: 'getTimegrid',
);
Future<TimetableGetTimegridResponse> getTimegrid({bool renew = false}) =>
TimetableGetTimegrid().run();
Future<GetCustomTimetableEventResponse> getCustomEvents({
bool renew = false,