import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'package:home_widget/home_widget.dart'; import 'widget_data.dart'; import 'widget_publisher.dart'; /// Bridge to the native widget host. All keys/names live here so the Kotlin /// and Swift sides stay in sync. class WidgetSync { static const String iosAppGroupId = 'group.eu.mhsl.marianum.mobile.client.widget'; // Must match the WidgetKit `kind` strings declared in // TimetableWidgetExtension.swift — a mismatch makes reloadTimelines a no-op. static const String iosDayWidgetKind = 'TimetableDayWidget'; static const String iosWeekWidgetKind = 'TimetableWeekWidget'; static const String androidDayProvider = 'TimetableDayWidget'; static const String androidWeekProvider = 'TimetableWeekWidget'; // Version suffix lets a schema change invalidate stale snapshots by // bumping the key instead of risking a parse crash. static const String dayDataKey = 'widget_data_day_v1'; // v2: 14-day window + per-day `days` holiday info. static const String weekDataKey = 'widget_data_week_v2'; static const String fetchedAtKey = 'widget_data_fetched_at_v1'; static const String loggedInKey = 'widget_data_logged_in_v1'; // Mirrored into widget storage so the background isolate can read it // without reopening HydratedBloc storage. static const String connectDoubleLessonsKey = 'widget_setting_connect_double_lessons_v1'; static const String themeModeKey = 'widget_setting_theme_mode_v1'; // Mirrored from CapabilitiesCubit so the background isolate can render // teacher plans (class instead of teacher name) without bloc storage. static const String isTeacherKey = 'widget_setting_is_teacher_v1'; // Mirrored so the background isolate hits the same Marianum-Connect base // URL the in-app settings cubit currently has selected. static const String marianumConnectBaseUrlKey = 'widget_setting_mc_base_url_v1'; static bool _initialised = false; static Future ensureInitialized() async { if (_initialised) return; await HomeWidget.setAppGroupId(iosAppGroupId); _initialised = true; } static Future writeDayData(WidgetTimetableData data) => _writeData(dayDataKey, data); static Future writeWeekData(WidgetTimetableData data) => _writeData(weekDataKey, data); /// Writes a complete snapshot and marks the widget signed in, then asks the /// native side to re-render. static Future publishSnapshot({ required WidgetTimetableData day, required WidgetTimetableData week, }) async { await writeDayData(day); await writeWeekData(week); await setLoggedIn(true); await triggerUpdate(); } static Future _writeData(String key, WidgetTimetableData data) async { await _setString(key, jsonEncode(data.toJson())); await _setString(fetchedAtKey, data.fetchedAt.toIso8601String()); } static Future setLoggedIn(bool loggedIn) => _setBool(loggedInKey, loggedIn); static Future setConnectDoubleLessons(bool value) => _setBool(connectDoubleLessonsKey, value); /// Default `true` matches `default_settings.dart` — fresh install behaves /// like the in-app calendar. static Future getConnectDoubleLessons() => _getBool(connectDoubleLessonsKey, defaultValue: true); static Future setIsTeacher(bool value) => _setBool(isTeacherKey, value); static Future getIsTeacher() => _getBool(isTeacherKey, defaultValue: false); static Future _setBool(String key, bool value) async { await ensureInitialized(); await HomeWidget.saveWidgetData(key, value); } static Future _getBool(String key, {required bool defaultValue}) async { await ensureInitialized(); return await HomeWidget.getWidgetData(key) ?? defaultValue; } static Future setThemeMode(String mode) => _setString(themeModeKey, mode); static Future setMarianumConnectBaseUrl(String url) => _setString(marianumConnectBaseUrlKey, url); static Future getMarianumConnectBaseUrl() => _getString(marianumConnectBaseUrlKey); static Future getFetchedAt() async { final raw = await _getString(fetchedAtKey); return raw == null ? null : DateTime.tryParse(raw); } static Future _setString(String key, String? value) async { await ensureInitialized(); await HomeWidget.saveWidgetData(key, value); } static Future _getString(String key) async { await ensureInitialized(); return HomeWidget.getWidgetData(key); } static Future clear() async { WidgetPublisher.resetDedupe(); await ensureInitialized(); await HomeWidget.saveWidgetData(dayDataKey, null); await HomeWidget.saveWidgetData(weekDataKey, null); await HomeWidget.saveWidgetData(fetchedAtKey, null); await HomeWidget.saveWidgetData(loggedInKey, false); } static Future triggerUpdate() async { await ensureInitialized(); try { await HomeWidget.updateWidget( androidName: androidDayProvider, iOSName: iosDayWidgetKind, ); await HomeWidget.updateWidget( androidName: androidWeekProvider, iOSName: iosWeekWidgetKind, ); } on Exception catch (e) { log('WidgetSync.triggerUpdate failed: $e'); } } }