simplified code across the app and consolidated duplicated helpers

This commit is contained in:
2026-09-27 17:29:54 +02:00
parent 3e856d0b1c
commit ee1240f71a
155 changed files with 1772 additions and 2546 deletions
@@ -1,16 +1,14 @@
import 'dart:developer';
import '../../../../../api/marianumconnect/queries/timetable_get_element_week/timetable_element_type.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
import '../../../../../extensions/date_time.dart';
import '../../../infrastructure/loadable_state/loadable_state.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 '../../timetable/bloc/timetable_event.dart';
import '../../timetable/bloc/timetable_state.dart';
import '../../timetable/bloc/timetable_week_loader.dart';
import '../../timetable/data_provider/timetable_data_provider.dart';
import '../repository/foreign_timetable_repository.dart';
/// Drives a foreign element's timetable. Mirrors `TimetableBloc`'s week-loading
/// Drives a foreign element's timetable. Shares `TimetableBloc`'s week loading
/// and navigation but loads weeks from the element endpoint, carries no custom
/// events, and does not persist (page-scoped, recreated per element). Reuses
/// [TimetableState] verbatim so the render pipeline is unchanged; `customEvents`
@@ -21,34 +19,25 @@ class ForeignTimetableBloc
TimetableEvent,
TimetableState,
ForeignTimetableRepository
> {
>
with TimetableWeekLoader<ForeignTimetableRepository> {
final TimetableElementType type;
// Named `elementId` rather than `id` to avoid shadowing HydratedMixin's
// `String get id` (the storage key), which a plain `int id` would illegally
// override.
final int elementId;
final String title;
DateTime _lastWeekRequestStart = DateTime.fromMillisecondsSinceEpoch(0);
ForeignTimetableBloc({
required this.type,
required this.elementId,
required this.title,
});
ForeignTimetableBloc({required this.type, required this.elementId});
@override
ForeignTimetableRepository repository() => ForeignTimetableRepository();
@override
TimetableState fromNothing() {
final reference = DateTime.now().addDays(2);
return TimetableState(
startDate: reference.mondayOfWeek,
endDate: _endOfWeek(reference),
);
}
TimetableDataProvider get referenceData => repo.data.referenceData;
@override
Future<TimetableGetWeekResponse> fetchWeek(DateTime start, DateTime end) =>
repo.data.getElementWeek(type, elementId, start, end);
// Persistence disabled: page-scoped and element-specific, nothing worth
// restoring. toJson returns null so nothing is written; fromJson starts fresh.
@@ -72,116 +61,5 @@ class ForeignTimetableBloc
Map<String, dynamic>? toStorage(TimetableState state) => null;
@override
Future<void> gatherData() async {
final initial = innerState ?? fromNothing();
Object? firstError;
void recordError(Object e) {
firstError ??= e;
}
await Future.wait([
_loadCurrentWeek(initial.startDate, initial.endDate, onError: recordError),
_loadStaticReferenceData(onError: recordError),
]);
if (firstError != null) throw firstError!;
add(DataGathered((s) => s));
_prefetchAdjacentWeeks(initial.startDate, initial.endDate);
}
void changeWeek(DateTime startDate, DateTime endDate) {
final current = innerState ?? fromNothing();
if (current.startDate == startDate && current.endDate == endDate) return;
add(Emit((s) => s.copyWith(startDate: startDate, endDate: endDate)));
_loadCurrentWeek(startDate, endDate);
_prefetchAdjacentWeeks(startDate, endDate);
}
void resetWeek() {
final reference = DateTime.now().addDays(2);
changeWeek(reference.mondayOfWeek, _endOfWeek(reference));
}
void refresh() => fetch();
Future<void> _loadCurrentWeek(
DateTime startDate,
DateTime endDate, {
void Function(Object)? onError,
}) async {
final requestStart = DateTime.now();
_lastWeekRequestStart = requestStart;
try {
final week = await repo.data.getElementWeek(
type,
elementId,
startDate,
endDate,
onError: onError,
);
if (_lastWeekRequestStart.isAfter(requestStart)) return;
_writeWeekToCache(startDate, week);
} catch (e) {
log('getElementWeek error for $startDate–$endDate: $e');
onError?.call(e);
}
}
Future<void> _loadStaticReferenceData({
void Function(Object)? onError,
}) async {
try {
final (rooms, subjects, schoolHolidays, schoolyear) = await (
repo.data.getRooms(onError: onError),
repo.data.getSubjects(onError: onError),
repo.data.getSchoolHolidays(onError: onError),
repo.data.getCurrentSchoolyear(onError: onError),
).wait;
add(
Emit(
(s) => s.withReferenceData(
rooms: rooms,
subjects: subjects,
schoolHolidays: schoolHolidays,
schoolyear: schoolyear,
),
),
);
} catch (e) {
onError?.call(e);
}
try {
final timegrid = await repo.data.getTimegrid();
add(Emit((s) => s.withReferenceData(timegrid: timegrid)));
} catch (_) {
// Timegrid load failure falls back to a hardcoded schedule in the UI.
}
}
void _prefetchAdjacentWeeks(DateTime start, DateTime end) {
_prefetchWeek(start.subtractDays(7), end.subtractDays(7));
_prefetchWeek(start.addDays(7), end.addDays(7));
}
void _prefetchWeek(DateTime start, DateTime end) {
repo.data
.getElementWeek(type, elementId, start, end)
.then((week) => _writeWeekToCache(start, week))
.catchError((_) {});
}
void _writeWeekToCache(DateTime weekStart, TimetableGetWeekResponse week) {
add(Emit((s) => s.withFetchedWeek(weekStart, week)));
}
static DateTime _endOfWeek(DateTime reference) {
final friday = reference.addDays(
DateTime.daysPerWeek - reference.weekday - 2,
);
return DateTime(friday.year, friday.month, friday.day);
}
Future<void> gatherData() => gatherWeek();
}
@@ -1,64 +1,27 @@
import '../../../../../api/marianumconnect/queries/timetable_get_element_week/timetable_element_type.dart';
import '../../../../../api/marianumconnect/queries/timetable_get_element_week/timetable_get_element_week.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 '../../timetable/data_provider/timetable_data_provider.dart';
/// Data access for a foreign element's timetable. The week comes from the
/// element-specific endpoint; all reference data (rooms/subjects/holidays/
/// school year/timegrid) is school-wide, so it delegates to the existing
/// [TimetableDataProvider] (which caches it). Custom events are intentionally
/// absent — they are user-private.
/// school year/timegrid) is school-wide and comes from [referenceData].
/// Custom events are intentionally absent — they are user-private.
class ForeignTimetableDataProvider {
final TimetableDataProvider _base;
final TimetableDataProvider referenceData;
ForeignTimetableDataProvider([TimetableDataProvider? base])
: _base = base ?? TimetableDataProvider();
ForeignTimetableDataProvider([TimetableDataProvider? referenceData])
: referenceData = referenceData ?? TimetableDataProvider();
Future<TimetableGetWeekResponse> getElementWeek(
TimetableElementType type,
int id,
DateTime startDate,
DateTime endDate, {
void Function(Object)? onError,
}) async {
try {
return await TimetableGetElementWeek().run(
type: type,
id: id,
from: startDate,
until: endDate,
);
} catch (e) {
onError?.call(e);
rethrow;
}
}
Future<TimetableGetRoomsResponse> getRooms({
void Function(Object)? onError,
bool renew = false,
}) => _base.getRooms(onError: onError, renew: renew);
Future<TimetableGetSubjectsResponse> getSubjects({
void Function(Object)? onError,
bool renew = false,
}) => _base.getSubjects(onError: onError, renew: renew);
Future<TimetableGetHolidaysResponse> getSchoolHolidays({
void Function(Object)? onError,
bool renew = false,
}) => _base.getSchoolHolidays(onError: onError, renew: renew);
Future<TimetableGetSchoolyearResponse> getCurrentSchoolyear({
void Function(Object)? onError,
bool renew = false,
}) => _base.getCurrentSchoolyear(onError: onError, renew: renew);
Future<TimetableGetTimegridResponse> getTimegrid({bool renew = false}) =>
_base.getTimegrid(renew: renew);
DateTime endDate,
) async => TimetableGetElementWeek().run(
type: type,
id: id,
from: startDate,
until: endDate,
);
}