import '../../../../../api/marianumconnect/queries/timetable_custom_events/custom_events_migration.dart'; import '../../../../../api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart'; import '../../../../../api/mhsl/custom_timetable_event/custom_timetable_event.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 '../data_provider/timetable_data_provider.dart'; import '../repository/timetable_repository.dart'; import 'timetable_event.dart'; import 'timetable_state.dart'; import 'timetable_week_loader.dart'; class TimetableBloc extends LoadableHydratedBloc< TimetableEvent, TimetableState, TimetableRepository > with TimetableWeekLoader { /// Set by [retry] to force the next [gatherData] to bypass the custom-event /// cache and actually hit the network. Cleared at the top of [gatherData]. bool _forceRenew = false; @override void retry() { _forceRenew = true; super.retry(); } @override TimetableRepository repository() => TimetableRepository(); @override TimetableDataProvider get referenceData => repo.data; @override Future fetchWeek(DateTime start, DateTime end) => repo.data.getWeek(start, end); /// Persisted state may carry a stale `startDate`/`endDate` from the user's /// 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 json) { final (start, end) = currentTimetableWeek(); return TimetableState.fromJson(json).copyWith( startDate: start, endDate: end, accessibleStartDate: null, accessibleEndDate: null, ); } @override Map? toStorage(TimetableState state) => state.toJson(); @override Future gatherData() { final renew = _forceRenew; _forceRenew = false; return gatherWeek( also: (onError) => _loadCustomEvents(onError: onError, renew: renew), ); } Future addCustomEvent(CustomTimetableEvent event) async { await repo.data.addCustomEvent(event); await _refreshCustomEvents(); } Future updateCustomEvent(String id, CustomTimetableEvent event) async { await repo.data.updateCustomEvent(id, event); await _refreshCustomEvents(); } Future removeCustomEvent(String id) async { await repo.data.removeCustomEvent(id); await _refreshCustomEvents(); } /// Setzt die persönliche Farbe eines Fachs (Schlüssel: Fach-Kürzel) und lädt /// die Fächer neu, damit die eingebettete Farbe im Kalender wirkt. Future setSubjectColor(String subjectShort, String color) async { await repo.data.setSubjectColor(subjectShort, color); await _refreshSubjects(); } Future clearSubjectColor(String subjectShort) async { await repo.data.removeSubjectColor(subjectShort); await _refreshSubjects(); } Future _refreshSubjects() async { final subjects = await repo.data.getSubjects(); add(DataGathered((s) => s.withReferenceData(subjects: subjects))); } Future _loadCustomEvents({ void Function(Object)? onError, bool renew = false, }) async { try { await CustomEventsMigration.runOnce(); final events = await repo.data.getCustomEvents( renew: renew, onError: onError, ); add(Emit((s) => s.withReferenceData(customEvents: events))); } catch (e) { onError?.call(e); } } Future _refreshCustomEvents() async { final events = await repo.data.getCustomEvents(renew: true); add(DataGathered((s) => s.withReferenceData(customEvents: events))); } }