implemented current schoolyear API and dynamic timetable scroll boundaries, added handling for out-of-range errors to narrow accessible dates, optimized holiday region rendering by collapsing overlaps, and refined holiday tile UI
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import '../../webuntis_api.dart';
|
||||||
|
import 'get_current_schoolyear_response.dart';
|
||||||
|
|
||||||
|
class GetCurrentSchoolyear extends WebuntisApi {
|
||||||
|
GetCurrentSchoolyear() : super('getCurrentSchoolyear', null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<GetCurrentSchoolyearResponse> run() async {
|
||||||
|
final rawAnswer = await query(this);
|
||||||
|
return finalize(
|
||||||
|
GetCurrentSchoolyearResponse.fromJson(
|
||||||
|
jsonDecode(rawAnswer) as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import '../../../request_cache.dart';
|
||||||
|
import 'get_current_schoolyear.dart';
|
||||||
|
import 'get_current_schoolyear_response.dart';
|
||||||
|
|
||||||
|
class GetCurrentSchoolyearCache
|
||||||
|
extends SimpleCache<GetCurrentSchoolyearResponse> {
|
||||||
|
GetCurrentSchoolyearCache({super.onUpdate, super.onError, super.renew})
|
||||||
|
: super(
|
||||||
|
cacheTime: RequestCache.cacheDay,
|
||||||
|
loader: () => GetCurrentSchoolyear().run(),
|
||||||
|
fromJson: GetCurrentSchoolyearResponse.fromJson,
|
||||||
|
) {
|
||||||
|
start('wu-current-schoolyear');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
import '../../../api_response.dart';
|
||||||
|
|
||||||
|
part 'get_current_schoolyear_response.g.dart';
|
||||||
|
|
||||||
|
/// Wraps Webuntis' `getCurrentSchoolyear` payload. The server returns a
|
||||||
|
/// single object with the current school year's bounds (yyyyMMdd integers).
|
||||||
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
class GetCurrentSchoolyearResponse extends ApiResponse {
|
||||||
|
GetCurrentSchoolyearResponseObject result;
|
||||||
|
|
||||||
|
GetCurrentSchoolyearResponse(this.result);
|
||||||
|
|
||||||
|
factory GetCurrentSchoolyearResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$GetCurrentSchoolyearResponseFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$GetCurrentSchoolyearResponseToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class GetCurrentSchoolyearResponseObject {
|
||||||
|
int id;
|
||||||
|
String name;
|
||||||
|
int startDate;
|
||||||
|
int endDate;
|
||||||
|
|
||||||
|
GetCurrentSchoolyearResponseObject(
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.startDate,
|
||||||
|
this.endDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory GetCurrentSchoolyearResponseObject.fromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$GetCurrentSchoolyearResponseObjectFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() =>
|
||||||
|
_$GetCurrentSchoolyearResponseObjectToJson(this);
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'get_current_schoolyear_response.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
GetCurrentSchoolyearResponse _$GetCurrentSchoolyearResponseFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) =>
|
||||||
|
GetCurrentSchoolyearResponse(
|
||||||
|
GetCurrentSchoolyearResponseObject.fromJson(
|
||||||
|
json['result'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, e as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$GetCurrentSchoolyearResponseToJson(
|
||||||
|
GetCurrentSchoolyearResponse instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'headers': ?instance.headers,
|
||||||
|
'result': instance.result.toJson(),
|
||||||
|
};
|
||||||
|
|
||||||
|
GetCurrentSchoolyearResponseObject _$GetCurrentSchoolyearResponseObjectFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => GetCurrentSchoolyearResponseObject(
|
||||||
|
(json['id'] as num).toInt(),
|
||||||
|
json['name'] as String,
|
||||||
|
(json['startDate'] as num).toInt(),
|
||||||
|
(json['endDate'] as num).toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$GetCurrentSchoolyearResponseObjectToJson(
|
||||||
|
GetCurrentSchoolyearResponseObject instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'startDate': instance.startDate,
|
||||||
|
'endDate': instance.endDate,
|
||||||
|
};
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import '../../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
|
import '../../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
|
||||||
import '../../../../../api/webuntis/queries/get_timetable/get_timetable_response.dart';
|
import '../../../../../api/webuntis/queries/get_timetable/get_timetable_response.dart';
|
||||||
|
import '../../../../../api/webuntis/webuntis_error.dart';
|
||||||
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc.dart';
|
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc.dart';
|
||||||
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc_event.dart';
|
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc_event.dart';
|
||||||
import '../repository/timetable_repository.dart';
|
import '../repository/timetable_repository.dart';
|
||||||
@@ -124,20 +127,65 @@ class TimetableBloc
|
|||||||
);
|
);
|
||||||
if (_lastWeekRequestStart.isAfter(requestStart)) return;
|
if (_lastWeekRequestStart.isAfter(requestStart)) return;
|
||||||
_writeWeekToCache(startDate, week);
|
_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) {
|
} catch (e) {
|
||||||
onError?.call(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.add(const Duration(days: 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.subtract(const Duration(days: 1));
|
||||||
|
final current = s.accessibleEndDate;
|
||||||
|
if (current != null && !candidate.isBefore(current)) return s;
|
||||||
|
return s.copyWith(accessibleEndDate: candidate);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadStaticReferenceData({
|
Future<void> _loadStaticReferenceData({
|
||||||
void Function(Object)? onError,
|
void Function(Object)? onError,
|
||||||
bool renew = false,
|
bool renew = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final (rooms, subjects, schoolHolidays) = await (
|
final (rooms, subjects, schoolHolidays, schoolyear) = await (
|
||||||
repo.data.getRooms(onError: onError, renew: renew),
|
repo.data.getRooms(onError: onError, renew: renew),
|
||||||
repo.data.getSubjects(onError: onError, renew: renew),
|
repo.data.getSubjects(onError: onError, renew: renew),
|
||||||
repo.data.getSchoolHolidays(onError: onError, renew: renew),
|
repo.data.getSchoolHolidays(onError: onError, renew: renew),
|
||||||
|
repo.data.getCurrentSchoolyear(onError: onError, renew: renew),
|
||||||
).wait;
|
).wait;
|
||||||
|
|
||||||
add(
|
add(
|
||||||
@@ -146,6 +194,7 @@ class TimetableBloc
|
|||||||
rooms: rooms,
|
rooms: rooms,
|
||||||
subjects: subjects,
|
subjects: subjects,
|
||||||
schoolHolidays: schoolHolidays,
|
schoolHolidays: schoolHolidays,
|
||||||
|
schoolyear: schoolyear,
|
||||||
dataVersion: s.dataVersion + 1,
|
dataVersion: s.dataVersion + 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import '../../../../../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_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_holidays/get_holidays_response.dart';
|
||||||
import '../../../../../api/webuntis/queries/get_rooms/get_rooms_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_subjects/get_subjects_response.dart';
|
||||||
@@ -20,11 +21,18 @@ abstract class TimetableState with _$TimetableState {
|
|||||||
GetRoomsResponse? rooms,
|
GetRoomsResponse? rooms,
|
||||||
GetSubjectsResponse? subjects,
|
GetSubjectsResponse? subjects,
|
||||||
GetHolidaysResponse? schoolHolidays,
|
GetHolidaysResponse? schoolHolidays,
|
||||||
|
GetCurrentSchoolyearResponse? schoolyear,
|
||||||
GetTimegridUnitsResponse? timegrid,
|
GetTimegridUnitsResponse? timegrid,
|
||||||
GetCustomTimetableEventResponse? customEvents,
|
GetCustomTimetableEventResponse? customEvents,
|
||||||
required DateTime startDate,
|
required DateTime startDate,
|
||||||
required DateTime endDate,
|
required DateTime endDate,
|
||||||
@Default(0) int dataVersion,
|
@Default(0) int dataVersion,
|
||||||
|
// Boundaries learned from `-7004 no allowed date` errors during scroll.
|
||||||
|
// 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.
|
||||||
|
DateTime? accessibleStartDate,
|
||||||
|
DateTime? accessibleEndDate,
|
||||||
}) = _TimetableState;
|
}) = _TimetableState;
|
||||||
|
|
||||||
factory TimetableState.fromJson(Map<String, Object?> json) =>
|
factory TimetableState.fromJson(Map<String, Object?> json) =>
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$TimetableState {
|
mixin _$TimetableState {
|
||||||
|
|
||||||
Map<String, GetTimetableResponse> get weekCache; GetRoomsResponse? get rooms; GetSubjectsResponse? get subjects; GetHolidaysResponse? get schoolHolidays; GetTimegridUnitsResponse? get timegrid; GetCustomTimetableEventResponse? get customEvents; DateTime get startDate; DateTime get endDate; int get dataVersion;
|
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.
|
||||||
|
// 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.
|
||||||
|
DateTime? get accessibleStartDate; DateTime? get accessibleEndDate;
|
||||||
/// Create a copy of TimetableState
|
/// Create a copy of TimetableState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -28,16 +32,16 @@ $TimetableStateCopyWith<TimetableState> get copyWith => _$TimetableStateCopyWith
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is TimetableState&&const DeepCollectionEquality().equals(other.weekCache, weekCache)&&(identical(other.rooms, rooms) || other.rooms == rooms)&&(identical(other.subjects, subjects) || other.subjects == subjects)&&(identical(other.schoolHolidays, schoolHolidays) || other.schoolHolidays == schoolHolidays)&&(identical(other.timegrid, timegrid) || other.timegrid == timegrid)&&(identical(other.customEvents, customEvents) || other.customEvents == customEvents)&&(identical(other.startDate, startDate) || other.startDate == startDate)&&(identical(other.endDate, endDate) || other.endDate == endDate)&&(identical(other.dataVersion, dataVersion) || other.dataVersion == dataVersion));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is TimetableState&&const DeepCollectionEquality().equals(other.weekCache, weekCache)&&(identical(other.rooms, rooms) || other.rooms == rooms)&&(identical(other.subjects, subjects) || other.subjects == subjects)&&(identical(other.schoolHolidays, schoolHolidays) || other.schoolHolidays == schoolHolidays)&&(identical(other.schoolyear, schoolyear) || other.schoolyear == schoolyear)&&(identical(other.timegrid, timegrid) || other.timegrid == timegrid)&&(identical(other.customEvents, customEvents) || other.customEvents == customEvents)&&(identical(other.startDate, startDate) || other.startDate == startDate)&&(identical(other.endDate, endDate) || other.endDate == endDate)&&(identical(other.dataVersion, dataVersion) || other.dataVersion == dataVersion)&&(identical(other.accessibleStartDate, accessibleStartDate) || other.accessibleStartDate == accessibleStartDate)&&(identical(other.accessibleEndDate, accessibleEndDate) || other.accessibleEndDate == accessibleEndDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(weekCache),rooms,subjects,schoolHolidays,timegrid,customEvents,startDate,endDate,dataVersion);
|
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(weekCache),rooms,subjects,schoolHolidays,schoolyear,timegrid,customEvents,startDate,endDate,dataVersion,accessibleStartDate,accessibleEndDate);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'TimetableState(weekCache: $weekCache, rooms: $rooms, subjects: $subjects, schoolHolidays: $schoolHolidays, timegrid: $timegrid, customEvents: $customEvents, startDate: $startDate, endDate: $endDate, dataVersion: $dataVersion)';
|
return 'TimetableState(weekCache: $weekCache, rooms: $rooms, subjects: $subjects, schoolHolidays: $schoolHolidays, schoolyear: $schoolyear, timegrid: $timegrid, customEvents: $customEvents, startDate: $startDate, endDate: $endDate, dataVersion: $dataVersion, accessibleStartDate: $accessibleStartDate, accessibleEndDate: $accessibleEndDate)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ abstract mixin class $TimetableStateCopyWith<$Res> {
|
|||||||
factory $TimetableStateCopyWith(TimetableState value, $Res Function(TimetableState) _then) = _$TimetableStateCopyWithImpl;
|
factory $TimetableStateCopyWith(TimetableState value, $Res Function(TimetableState) _then) = _$TimetableStateCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
Map<String, GetTimetableResponse> weekCache, GetRoomsResponse? rooms, GetSubjectsResponse? subjects, GetHolidaysResponse? schoolHolidays, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion
|
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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -65,18 +69,21 @@ class _$TimetableStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of TimetableState
|
/// Create a copy of TimetableState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? weekCache = null,Object? rooms = freezed,Object? subjects = freezed,Object? schoolHolidays = freezed,Object? timegrid = freezed,Object? customEvents = freezed,Object? startDate = null,Object? endDate = null,Object? dataVersion = null,}) {
|
@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(
|
return _then(_self.copyWith(
|
||||||
weekCache: null == weekCache ? _self.weekCache : weekCache // ignore: cast_nullable_to_non_nullable
|
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 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 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 GetSubjectsResponse?,schoolHolidays: freezed == schoolHolidays ? _self.schoolHolidays : schoolHolidays // ignore: cast_nullable_to_non_nullable
|
||||||
as GetHolidaysResponse?,timegrid: freezed == timegrid ? _self.timegrid : timegrid // 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 GetTimegridUnitsResponse?,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 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,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
|
as DateTime,dataVersion: null == dataVersion ? _self.dataVersion : dataVersion // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,accessibleStartDate: freezed == accessibleStartDate ? _self.accessibleStartDate : accessibleStartDate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,accessibleEndDate: freezed == accessibleEndDate ? _self.accessibleEndDate : accessibleEndDate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,10 +168,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Map<String, GetTimetableResponse> weekCache, GetRoomsResponse? rooms, GetSubjectsResponse? subjects, GetHolidaysResponse? schoolHolidays, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion)? $default,{required TResult orElse(),}) {final _that = this;
|
@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;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _TimetableState() when $default != null:
|
case _TimetableState() when $default != null:
|
||||||
return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,_that.timegrid,_that.customEvents,_that.startDate,_that.endDate,_that.dataVersion);case _:
|
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 _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -182,10 +189,10 @@ 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, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion) $default,) {final _that = this;
|
@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;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _TimetableState():
|
case _TimetableState():
|
||||||
return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,_that.timegrid,_that.customEvents,_that.startDate,_that.endDate,_that.dataVersion);case _:
|
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 _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -202,10 +209,10 @@ 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, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion)? $default,) {final _that = this;
|
@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;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _TimetableState() when $default != null:
|
case _TimetableState() when $default != null:
|
||||||
return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,_that.timegrid,_that.customEvents,_that.startDate,_that.endDate,_that.dataVersion);case _:
|
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 _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -217,7 +224,7 @@ return $default(_that.weekCache,_that.rooms,_that.subjects,_that.schoolHolidays,
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _TimetableState extends TimetableState {
|
class _TimetableState extends TimetableState {
|
||||||
const _TimetableState({final Map<String, GetTimetableResponse> weekCache = const <String, GetTimetableResponse>{}, this.rooms, this.subjects, this.schoolHolidays, this.timegrid, this.customEvents, required this.startDate, required this.endDate, this.dataVersion = 0}): _weekCache = weekCache,super._();
|
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._();
|
||||||
factory _TimetableState.fromJson(Map<String, dynamic> json) => _$TimetableStateFromJson(json);
|
factory _TimetableState.fromJson(Map<String, dynamic> json) => _$TimetableStateFromJson(json);
|
||||||
|
|
||||||
final Map<String, GetTimetableResponse> _weekCache;
|
final Map<String, GetTimetableResponse> _weekCache;
|
||||||
@@ -230,11 +237,18 @@ class _TimetableState extends TimetableState {
|
|||||||
@override final GetRoomsResponse? rooms;
|
@override final GetRoomsResponse? rooms;
|
||||||
@override final GetSubjectsResponse? subjects;
|
@override final GetSubjectsResponse? subjects;
|
||||||
@override final GetHolidaysResponse? schoolHolidays;
|
@override final GetHolidaysResponse? schoolHolidays;
|
||||||
|
@override final GetCurrentSchoolyearResponse? schoolyear;
|
||||||
@override final GetTimegridUnitsResponse? timegrid;
|
@override final GetTimegridUnitsResponse? timegrid;
|
||||||
@override final GetCustomTimetableEventResponse? customEvents;
|
@override final GetCustomTimetableEventResponse? customEvents;
|
||||||
@override final DateTime startDate;
|
@override final DateTime startDate;
|
||||||
@override final DateTime endDate;
|
@override final DateTime endDate;
|
||||||
@override@JsonKey() final int dataVersion;
|
@override@JsonKey() final int dataVersion;
|
||||||
|
// Boundaries learned from `-7004 no allowed date` errors during scroll.
|
||||||
|
// 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.
|
||||||
|
@override final DateTime? accessibleStartDate;
|
||||||
|
@override final DateTime? accessibleEndDate;
|
||||||
|
|
||||||
/// Create a copy of TimetableState
|
/// Create a copy of TimetableState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -249,16 +263,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TimetableState&&const DeepCollectionEquality().equals(other._weekCache, _weekCache)&&(identical(other.rooms, rooms) || other.rooms == rooms)&&(identical(other.subjects, subjects) || other.subjects == subjects)&&(identical(other.schoolHolidays, schoolHolidays) || other.schoolHolidays == schoolHolidays)&&(identical(other.timegrid, timegrid) || other.timegrid == timegrid)&&(identical(other.customEvents, customEvents) || other.customEvents == customEvents)&&(identical(other.startDate, startDate) || other.startDate == startDate)&&(identical(other.endDate, endDate) || other.endDate == endDate)&&(identical(other.dataVersion, dataVersion) || other.dataVersion == dataVersion));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TimetableState&&const DeepCollectionEquality().equals(other._weekCache, _weekCache)&&(identical(other.rooms, rooms) || other.rooms == rooms)&&(identical(other.subjects, subjects) || other.subjects == subjects)&&(identical(other.schoolHolidays, schoolHolidays) || other.schoolHolidays == schoolHolidays)&&(identical(other.schoolyear, schoolyear) || other.schoolyear == schoolyear)&&(identical(other.timegrid, timegrid) || other.timegrid == timegrid)&&(identical(other.customEvents, customEvents) || other.customEvents == customEvents)&&(identical(other.startDate, startDate) || other.startDate == startDate)&&(identical(other.endDate, endDate) || other.endDate == endDate)&&(identical(other.dataVersion, dataVersion) || other.dataVersion == dataVersion)&&(identical(other.accessibleStartDate, accessibleStartDate) || other.accessibleStartDate == accessibleStartDate)&&(identical(other.accessibleEndDate, accessibleEndDate) || other.accessibleEndDate == accessibleEndDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_weekCache),rooms,subjects,schoolHolidays,timegrid,customEvents,startDate,endDate,dataVersion);
|
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_weekCache),rooms,subjects,schoolHolidays,schoolyear,timegrid,customEvents,startDate,endDate,dataVersion,accessibleStartDate,accessibleEndDate);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'TimetableState(weekCache: $weekCache, rooms: $rooms, subjects: $subjects, schoolHolidays: $schoolHolidays, timegrid: $timegrid, customEvents: $customEvents, startDate: $startDate, endDate: $endDate, dataVersion: $dataVersion)';
|
return 'TimetableState(weekCache: $weekCache, rooms: $rooms, subjects: $subjects, schoolHolidays: $schoolHolidays, schoolyear: $schoolyear, timegrid: $timegrid, customEvents: $customEvents, startDate: $startDate, endDate: $endDate, dataVersion: $dataVersion, accessibleStartDate: $accessibleStartDate, accessibleEndDate: $accessibleEndDate)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -269,7 +283,7 @@ abstract mixin class _$TimetableStateCopyWith<$Res> implements $TimetableStateCo
|
|||||||
factory _$TimetableStateCopyWith(_TimetableState value, $Res Function(_TimetableState) _then) = __$TimetableStateCopyWithImpl;
|
factory _$TimetableStateCopyWith(_TimetableState value, $Res Function(_TimetableState) _then) = __$TimetableStateCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
Map<String, GetTimetableResponse> weekCache, GetRoomsResponse? rooms, GetSubjectsResponse? subjects, GetHolidaysResponse? schoolHolidays, GetTimegridUnitsResponse? timegrid, GetCustomTimetableEventResponse? customEvents, DateTime startDate, DateTime endDate, int dataVersion
|
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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -286,18 +300,21 @@ class __$TimetableStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of TimetableState
|
/// Create a copy of TimetableState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? weekCache = null,Object? rooms = freezed,Object? subjects = freezed,Object? schoolHolidays = freezed,Object? timegrid = freezed,Object? customEvents = freezed,Object? startDate = null,Object? endDate = null,Object? dataVersion = null,}) {
|
@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(
|
return _then(_TimetableState(
|
||||||
weekCache: null == weekCache ? _self._weekCache : weekCache // ignore: cast_nullable_to_non_nullable
|
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 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 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 GetSubjectsResponse?,schoolHolidays: freezed == schoolHolidays ? _self.schoolHolidays : schoolHolidays // ignore: cast_nullable_to_non_nullable
|
||||||
as GetHolidaysResponse?,timegrid: freezed == timegrid ? _self.timegrid : timegrid // 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 GetTimegridUnitsResponse?,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 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,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
|
as DateTime,dataVersion: null == dataVersion ? _self.dataVersion : dataVersion // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,accessibleStartDate: freezed == accessibleStartDate ? _self.accessibleStartDate : accessibleStartDate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,accessibleEndDate: freezed == accessibleEndDate ? _self.accessibleEndDate : accessibleEndDate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ _TimetableState _$TimetableStateFromJson(Map<String, dynamic> json) =>
|
|||||||
: GetHolidaysResponse.fromJson(
|
: GetHolidaysResponse.fromJson(
|
||||||
json['schoolHolidays'] as Map<String, dynamic>,
|
json['schoolHolidays'] as Map<String, dynamic>,
|
||||||
),
|
),
|
||||||
|
schoolyear: json['schoolyear'] == null
|
||||||
|
? null
|
||||||
|
: GetCurrentSchoolyearResponse.fromJson(
|
||||||
|
json['schoolyear'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
timegrid: json['timegrid'] == null
|
timegrid: json['timegrid'] == null
|
||||||
? null
|
? null
|
||||||
: GetTimegridUnitsResponse.fromJson(
|
: GetTimegridUnitsResponse.fromJson(
|
||||||
@@ -42,6 +47,12 @@ _TimetableState _$TimetableStateFromJson(Map<String, dynamic> json) =>
|
|||||||
startDate: DateTime.parse(json['startDate'] as String),
|
startDate: DateTime.parse(json['startDate'] as String),
|
||||||
endDate: DateTime.parse(json['endDate'] as String),
|
endDate: DateTime.parse(json['endDate'] as String),
|
||||||
dataVersion: (json['dataVersion'] as num?)?.toInt() ?? 0,
|
dataVersion: (json['dataVersion'] as num?)?.toInt() ?? 0,
|
||||||
|
accessibleStartDate: json['accessibleStartDate'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['accessibleStartDate'] as String),
|
||||||
|
accessibleEndDate: json['accessibleEndDate'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['accessibleEndDate'] as String),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$TimetableStateToJson(_TimetableState instance) =>
|
Map<String, dynamic> _$TimetableStateToJson(_TimetableState instance) =>
|
||||||
@@ -50,9 +61,12 @@ Map<String, dynamic> _$TimetableStateToJson(_TimetableState instance) =>
|
|||||||
'rooms': instance.rooms,
|
'rooms': instance.rooms,
|
||||||
'subjects': instance.subjects,
|
'subjects': instance.subjects,
|
||||||
'schoolHolidays': instance.schoolHolidays,
|
'schoolHolidays': instance.schoolHolidays,
|
||||||
|
'schoolyear': instance.schoolyear,
|
||||||
'timegrid': instance.timegrid,
|
'timegrid': instance.timegrid,
|
||||||
'customEvents': instance.customEvents,
|
'customEvents': instance.customEvents,
|
||||||
'startDate': instance.startDate.toIso8601String(),
|
'startDate': instance.startDate.toIso8601String(),
|
||||||
'endDate': instance.endDate.toIso8601String(),
|
'endDate': instance.endDate.toIso8601String(),
|
||||||
'dataVersion': instance.dataVersion,
|
'dataVersion': instance.dataVersion,
|
||||||
|
'accessibleStartDate': instance.accessibleStartDate?.toIso8601String(),
|
||||||
|
'accessibleEndDate': instance.accessibleEndDate?.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ 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.dart';
|
||||||
import '../../../../../api/mhsl/custom_timetable_event/update/update_custom_timetable_event_params.dart';
|
import '../../../../../api/mhsl/custom_timetable_event/update/update_custom_timetable_event_params.dart';
|
||||||
import '../../../../../api/request_cache.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_cache.dart';
|
||||||
import '../../../../../api/webuntis/queries/get_holidays/get_holidays_response.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_cache.dart';
|
||||||
@@ -73,6 +75,19 @@ class TimetableDataProvider {
|
|||||||
operationName: 'getSchoolHolidays',
|
operationName: 'getSchoolHolidays',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<GetCurrentSchoolyearResponse> getCurrentSchoolyear({
|
||||||
|
void Function(Object)? onError,
|
||||||
|
bool renew = false,
|
||||||
|
}) => resolveFromCache<GetCurrentSchoolyearResponse>(
|
||||||
|
(onUpdate, onError) => GetCurrentSchoolyearCache(
|
||||||
|
renew: renew,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
onError: onError,
|
||||||
|
),
|
||||||
|
onError: onError,
|
||||||
|
operationName: 'getCurrentSchoolyear',
|
||||||
|
);
|
||||||
|
|
||||||
Future<GetTimegridUnitsResponse> getTimegrid({bool renew = false}) =>
|
Future<GetTimegridUnitsResponse> getTimegrid({bool renew = false}) =>
|
||||||
resolveFromCache<GetTimegridUnitsResponse>(
|
resolveFromCache<GetTimegridUnitsResponse>(
|
||||||
(onUpdate, _) =>
|
(onUpdate, _) =>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
||||||
|
|
||||||
import '../../../extensions/date_time.dart';
|
|
||||||
import '../../../routing/app_routes.dart';
|
import '../../../routing/app_routes.dart';
|
||||||
import '../../../state/app/infrastructure/loadable_state/view/loadable_state_consumer.dart';
|
import '../../../state/app/infrastructure/loadable_state/view/loadable_state_consumer.dart';
|
||||||
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
@@ -13,6 +12,7 @@ import 'custom_events/custom_event_edit_dialog.dart';
|
|||||||
import 'data/arbitrary_appointment.dart';
|
import 'data/arbitrary_appointment.dart';
|
||||||
import 'data/lesson_period_schedule.dart';
|
import 'data/lesson_period_schedule.dart';
|
||||||
import 'data/timetable_appointment_factory.dart';
|
import 'data/timetable_appointment_factory.dart';
|
||||||
|
import 'data/webuntis_time.dart';
|
||||||
import 'details/appointment_details_dispatcher.dart';
|
import 'details/appointment_details_dispatcher.dart';
|
||||||
import 'widgets/custom_workweek_calendar.dart';
|
import 'widgets/custom_workweek_calendar.dart';
|
||||||
import 'widgets/special_regions_builder.dart';
|
import 'widgets/special_regions_builder.dart';
|
||||||
@@ -147,18 +147,20 @@ class _TimetableState extends State<Timetable> {
|
|||||||
disabledColor: Theme.of(context).disabledColor,
|
disabledColor: Theme.of(context).disabledColor,
|
||||||
).build();
|
).build();
|
||||||
|
|
||||||
|
// Scroll bounds follow the Webuntis school-year API: the calendar lets
|
||||||
|
// the user navigate every week the server has data for. A two-week
|
||||||
|
// fallback is used only while the school-year payload hasn't loaded yet
|
||||||
|
// (first launch / offline), so the calendar still mounts.
|
||||||
|
final (minDate, maxDate) = _scrollBounds(state);
|
||||||
|
|
||||||
return CustomWorkWeekCalendar(
|
return CustomWorkWeekCalendar(
|
||||||
key: _calendarKey,
|
key: _calendarKey,
|
||||||
schedule: schedule,
|
schedule: schedule,
|
||||||
appointments: appointments,
|
appointments: appointments,
|
||||||
timeRegions: regions,
|
timeRegions: regions,
|
||||||
initialDate: _initialDisplayDate(),
|
initialDate: _initialDisplayDate(),
|
||||||
minDate: DateTime.now()
|
minDate: minDate,
|
||||||
.subtract(const Duration(days: 14))
|
maxDate: maxDate,
|
||||||
.nextWeekday(DateTime.sunday),
|
|
||||||
maxDate: DateTime.now()
|
|
||||||
.add(const Duration(days: 7))
|
|
||||||
.nextWeekday(DateTime.saturday),
|
|
||||||
onAppointmentTap: (apt) =>
|
onAppointmentTap: (apt) =>
|
||||||
AppointmentDetailsDispatcher.show(context, bloc, apt),
|
AppointmentDetailsDispatcher.show(context, bloc, apt),
|
||||||
onWeekChanged: (start, end) => bloc.changeWeek(start, end),
|
onWeekChanged: (start, end) => bloc.changeWeek(start, end),
|
||||||
@@ -175,4 +177,43 @@ class _TimetableState extends State<Timetable> {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the (minDate, maxDate) the user is allowed to scroll between.
|
||||||
|
/// Starts from the Webuntis school year (or a tight window when that
|
||||||
|
/// hasn't loaded yet) and tightens by anything the bloc has learned
|
||||||
|
/// from `-7004 no allowed date` errors during scroll — so the user
|
||||||
|
/// can't slide off into territory Webuntis would refuse anyway.
|
||||||
|
///
|
||||||
|
/// minDate is snapped *forward* to the next Monday because the calendar's
|
||||||
|
/// internal `_mondayOf()` would otherwise pull a mid-week minDate back
|
||||||
|
/// into the just-rejected week. maxDate is passed through unsnapped —
|
||||||
|
/// `_mondayOf()` correctly walks back to the Monday of its own week,
|
||||||
|
/// which is the last fully-allowed week.
|
||||||
|
(DateTime, DateTime) _scrollBounds(TimetableState state) {
|
||||||
|
final year = state.schoolyear?.result;
|
||||||
|
final DateTime baseMin;
|
||||||
|
final DateTime baseMax;
|
||||||
|
if (year != null) {
|
||||||
|
baseMin = WebuntisTime.parse(year.startDate, 0);
|
||||||
|
baseMax = WebuntisTime.parse(year.endDate, 0);
|
||||||
|
} else {
|
||||||
|
final now = DateTime.now();
|
||||||
|
baseMin = now.subtract(const Duration(days: 14));
|
||||||
|
baseMax = now.add(const Duration(days: 7));
|
||||||
|
}
|
||||||
|
final effectiveMin = state.accessibleStartDate != null
|
||||||
|
? (state.accessibleStartDate!.isAfter(baseMin)
|
||||||
|
? state.accessibleStartDate!
|
||||||
|
: baseMin)
|
||||||
|
: baseMin;
|
||||||
|
final effectiveMax = state.accessibleEndDate != null
|
||||||
|
? (state.accessibleEndDate!.isBefore(baseMax)
|
||||||
|
? state.accessibleEndDate!
|
||||||
|
: baseMax)
|
||||||
|
: baseMax;
|
||||||
|
final daysToMonday =
|
||||||
|
(DateTime.monday - effectiveMin.weekday) % DateTime.daysPerWeek;
|
||||||
|
final mondayMin = effectiveMin.add(Duration(days: daysToMonday));
|
||||||
|
return (mondayMin, effectiveMax);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,40 @@ class CustomWorkWeekCalendarState extends State<CustomWorkWeekCalendar> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant CustomWorkWeekCalendar oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.minDate == oldWidget.minDate &&
|
||||||
|
widget.maxDate == oldWidget.maxDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Boundaries changed (e.g. school-year payload finished loading after
|
||||||
|
// the initial mount with the conservative fallback). Recompute the
|
||||||
|
// range and snap the controller to the page that still represents the
|
||||||
|
// currently visible week so the user doesn't get yanked around.
|
||||||
|
final newFirstMonday = _mondayOf(widget.minDate);
|
||||||
|
final newLastMonday = _mondayOf(widget.maxDate);
|
||||||
|
final newTotalWeeks =
|
||||||
|
newLastMonday.difference(newFirstMonday).inDays ~/ 7 + 1;
|
||||||
|
final visibleWeekStart = _firstMonday.add(
|
||||||
|
Duration(days: _currentWeekIndex * 7),
|
||||||
|
);
|
||||||
|
final newIndex = visibleWeekStart
|
||||||
|
.difference(newFirstMonday)
|
||||||
|
.inDays
|
||||||
|
~/
|
||||||
|
7;
|
||||||
|
final clampedIndex = newIndex.clamp(0, newTotalWeeks - 1);
|
||||||
|
final oldController = _pageController;
|
||||||
|
_pageController = PageController(initialPage: clampedIndex);
|
||||||
|
oldController.dispose();
|
||||||
|
setState(() {
|
||||||
|
_firstMonday = newFirstMonday;
|
||||||
|
_totalWeeks = newTotalWeeks;
|
||||||
|
_currentWeekIndex = clampedIndex;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_pageController.dispose();
|
_pageController.dispose();
|
||||||
|
|||||||
@@ -62,9 +62,13 @@ class SpecialRegionsBuilder {
|
|||||||
|
|
||||||
static String _dayKey(DateTime d) => '${d.year}-${d.month}-${d.day}';
|
static String _dayKey(DateTime d) => '${d.year}-${d.month}-${d.day}';
|
||||||
|
|
||||||
Iterable<TimeRegion> _buildHolidayRegions() => holidays.result.expand((
|
Iterable<TimeRegion> _buildHolidayRegions() {
|
||||||
holiday,
|
// Multiple Webuntis holiday entries can cover the same day (e.g. a
|
||||||
) {
|
// public holiday falling inside a vacation period). Collapse them
|
||||||
|
// per-day so we emit exactly one TimeRegion per day and the
|
||||||
|
// overlapping labels don't render on top of each other.
|
||||||
|
final byDay = <String, _HolidayDay>{};
|
||||||
|
for (final holiday in holidays.result) {
|
||||||
final startDay = WebuntisTime.parse(holiday.startDate, 0);
|
final startDay = WebuntisTime.parse(holiday.startDate, 0);
|
||||||
final endDay = WebuntisTime.parse(holiday.endDate, 0);
|
final endDay = WebuntisTime.parse(holiday.endDate, 0);
|
||||||
// Webuntis treats endDate inclusively (last day of the break) — the
|
// Webuntis treats endDate inclusively (last day of the break) — the
|
||||||
@@ -72,23 +76,33 @@ class SpecialRegionsBuilder {
|
|||||||
// and the final day of a multi-day vacation, both of which would
|
// and the final day of a multi-day vacation, both of which would
|
||||||
// otherwise be skipped.
|
// otherwise be skipped.
|
||||||
final dayCount = endDay.difference(startDay).inDays + 1;
|
final dayCount = endDay.difference(startDay).inDays + 1;
|
||||||
final days = List<DateTime>.generate(
|
for (var i = 0; i < dayCount; i++) {
|
||||||
dayCount,
|
final day = startDay.add(Duration(days: i));
|
||||||
(i) => startDay.add(Duration(days: i)),
|
final key = _dayKey(day);
|
||||||
|
byDay.putIfAbsent(key, () => _HolidayDay(day, [])).names.add(
|
||||||
|
holiday.name,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
final gridStartHour = kCalendarStartHour.floor();
|
final gridStartHour = kCalendarStartHour.floor();
|
||||||
final gridStartMinute = ((kCalendarStartHour - gridStartHour) * 60).round();
|
final gridStartMinute = ((kCalendarStartHour - gridStartHour) * 60).round();
|
||||||
final gridEndHour = kCalendarEndHour.floor();
|
final gridEndHour = kCalendarEndHour.floor();
|
||||||
final gridEndMinute = ((kCalendarEndHour - gridEndHour) * 60).round();
|
final gridEndMinute = ((kCalendarEndHour - gridEndHour) * 60).round();
|
||||||
return days.map(
|
return byDay.values.map(
|
||||||
(day) => TimeRegion(
|
(entry) => TimeRegion(
|
||||||
startTime: day.copyWith(hour: gridStartHour, minute: gridStartMinute),
|
startTime: entry.day.copyWith(
|
||||||
endTime: day.copyWith(hour: gridEndHour, minute: gridEndMinute),
|
hour: gridStartHour,
|
||||||
text: '$kTimeRegionHolidayPrefix${holiday.name}',
|
minute: gridStartMinute,
|
||||||
|
),
|
||||||
|
endTime: entry.day.copyWith(
|
||||||
|
hour: gridEndHour,
|
||||||
|
minute: gridEndMinute,
|
||||||
|
),
|
||||||
|
text: '$kTimeRegionHolidayPrefix${entry.names.join(" / ")}',
|
||||||
color: disabledColor.withAlpha(50),
|
color: disabledColor.withAlpha(50),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
TimeRegion _breakRegion(DateTime start, Duration duration) => TimeRegion(
|
TimeRegion _breakRegion(DateTime start, Duration duration) => TimeRegion(
|
||||||
startTime: start,
|
startTime: start,
|
||||||
@@ -98,3 +112,9 @@ class SpecialRegionsBuilder {
|
|||||||
iconData: Icons.restaurant,
|
iconData: Icons.restaurant,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _HolidayDay {
|
||||||
|
final DateTime day;
|
||||||
|
final List<String> names;
|
||||||
|
_HolidayDay(this.day, this.names);
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ class TimeRegionTile extends StatelessWidget {
|
|||||||
quarterTurns: 1,
|
quarterTurns: 1,
|
||||||
child: Text(
|
child: Text(
|
||||||
text.substring(kTimeRegionHolidayPrefix.length),
|
text.substring(kTimeRegionHolidayPrefix.length),
|
||||||
maxLines: 1,
|
maxLines: 2,
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
|||||||
Reference in New Issue
Block a user