implemented DST-safe date arithmetic with new addDays and subtractDays extensions, updated timetable state to reset view and scroll boundaries on initialization to prevent stale views, added hard caps to calendar navigation, and updated version to 1.0.3+52

This commit is contained in:
2026-05-22 15:08:30 +02:00
parent f185b3273a
commit 2858f910c9
10 changed files with 158 additions and 61 deletions
@@ -5,6 +5,7 @@ import 'package:intl/intl.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';
import '../repository/timetable_repository.dart';
@@ -18,7 +19,6 @@ class TimetableBloc
TimetableState,
TimetableRepository
> {
static const Duration _weekSpan = Duration(days: 7);
static final DateFormat _weekKeyFormat = DateFormat('yyyyMMdd');
DateTime _lastWeekRequestStart = DateTime.fromMillisecondsSinceEpoch(0);
@@ -38,16 +38,31 @@ class TimetableBloc
@override
TimetableState fromNothing() {
final reference = DateTime.now().add(const Duration(days: 2));
final reference = DateTime.now().addDays(2);
return TimetableState(
startDate: _startOfWeek(reference),
endDate: _endOfWeek(reference),
);
}
/// 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.
@override
TimetableState fromStorage(Map<String, dynamic> json) =>
TimetableState.fromJson(json);
TimetableState fromStorage(Map<String, dynamic> json) {
final stored = TimetableState.fromJson(json);
final reference = DateTime.now().addDays(2);
return stored.copyWith(
startDate: _startOfWeek(reference),
endDate: _endOfWeek(reference),
accessibleStartDate: null,
accessibleEndDate: null,
);
}
@override
Map<String, dynamic>? toStorage(TimetableState state) => state.toJson();
@@ -89,7 +104,7 @@ class TimetableBloc
}
void resetWeek() {
final reference = DateTime.now().add(const Duration(days: 2));
final reference = DateTime.now().addDays(2);
changeWeek(_startOfWeek(reference), _endOfWeek(reference));
}
@@ -161,14 +176,14 @@ class TimetableBloc
add(
Emit((s) {
if (isPast) {
final candidate = endDate.add(const Duration(days: 1));
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.subtract(const Duration(days: 1));
final candidate = startDate.subtractDays(1);
final current = s.accessibleEndDate;
if (current != null && !candidate.isBefore(current)) return s;
return s.copyWith(accessibleEndDate: candidate);
@@ -245,8 +260,8 @@ class TimetableBloc
}
void _prefetchAdjacentWeeks(DateTime start, DateTime end) {
_prefetchWeek(start.subtract(_weekSpan), end.subtract(_weekSpan));
_prefetchWeek(start.add(_weekSpan), end.add(_weekSpan));
_prefetchWeek(start.subtractDays(7), end.subtractDays(7));
_prefetchWeek(start.addDays(7), end.addDays(7));
}
void _prefetchWeek(DateTime start, DateTime end) {
@@ -268,13 +283,13 @@ class TimetableBloc
}
static DateTime _startOfWeek(DateTime reference) {
final monday = reference.subtract(Duration(days: reference.weekday - 1));
final monday = reference.subtractDays(reference.weekday - 1);
return DateTime(monday.year, monday.month, monday.day);
}
static DateTime _endOfWeek(DateTime reference) {
final friday = reference.add(
Duration(days: DateTime.daysPerWeek - reference.weekday - 2),
final friday = reference.addDays(
DateTime.daysPerWeek - reference.weekday - 2,
);
return DateTime(friday.year, friday.month, friday.day);
}