From 832c19ad8c629f1d577304262b41bfd5381ad38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20M=C3=BChlberger?= Date: Tue, 14 Apr 2026 10:09:00 +0200 Subject: [PATCH 1/3] feat: add device calendar dependency and platform permissions - add device_calendar dependency and resolved lockfiles - configure Android calendar permissions and ProGuard keep rules - add iOS calendar usage descriptions and updated Podfile.lock --- android/app/build.gradle.kts | 2 +- android/app/proguard-rules.pro | 1 + android/app/src/main/AndroidManifest.xml | 2 ++ ios/Podfile.lock | 6 ++++++ ios/Runner/Info.plist | 4 ++++ pubspec.lock | 22 +++++++++++++++++++--- pubspec.yaml | 4 ++++ 7 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 android/app/proguard-rules.pro diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 065b0d73..c3d1a28b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -56,7 +56,7 @@ android { signingConfig = signingConfigs.getByName("release") isMinifyEnabled = true isShrinkResources = true - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt")) + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } else { signingConfig = signingConfigs.getByName("debug") } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 00000000..d7668e11 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1 @@ +-keep class com.builttoroam.devicecalendar.** { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1c27f019..23d607c5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,8 @@ + + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b1d27667..73df9a59 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - device_calendar (0.0.1): + - Flutter - device_info_plus (0.0.1): - Flutter - Firebase/CoreOnly (12.8.0): @@ -148,6 +150,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - device_calendar (from `.symlinks/plugins/device_calendar/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) @@ -191,6 +194,8 @@ SPEC REPOS: - sqlite3 EXTERNAL SOURCES: + device_calendar: + :path: ".symlinks/plugins/device_calendar/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" firebase_core: @@ -233,6 +238,7 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/video_player_avfoundation/darwin" SPEC CHECKSUMS: + device_calendar: b55b2c5406cfba45c95a59f9059156daee1f74ed device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Firebase: 9a58fdbc9d8655ed7b79a19cf9690bb007d3d46d firebase_core: ee30637e6744af8e0c12a6a1e8a9718506ec2398 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index db75a3c5..3f0ac3ee 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,6 +45,10 @@ To provide you with the closest study rooms, cafeteria and departures we need to access your location while in use NSLocationAlwaysAndWhenInUseUsageDescription To provide you with the closest study rooms, cafeteria and departures we need to access your location + NSCalendarsUsageDescription + To sync your TUM calendar events with your device calendar + NSCalendarsFullAccessUsageDescription + To sync your TUM calendar events with your device calendar NSContactsUsageDescription To save TUM contacts to your local contacts we need access to your contacts UIApplicationSupportsIndirectInputEvents diff --git a/pubspec.lock b/pubspec.lock index 444d62c8..a13f8b95 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -281,6 +281,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + device_calendar: + dependency: "direct main" + description: + name: device_calendar + sha256: "683fb93ec302b6a65c0ce57df40ff9dcc2404f59c67a2f8b93e59318c8a0a225" + url: "https://pub.dev" + source: hosted + version: "4.3.3" device_info_plus: dependency: "direct main" description: @@ -1381,6 +1389,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: transitive description: @@ -1574,13 +1590,13 @@ packages: source: hosted version: "3.7.1" timezone: - dependency: transitive + dependency: "direct overridden" description: name: timezone - sha256: "784a5e34d2eb62e1326f24d6f600aaaee452eb8ca8ef2f384a59244e292d158b" + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 url: "https://pub.dev" source: hosted - version: "0.11.0" + version: "0.10.1" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6b00d368..a13f7f62 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,9 @@ dependencies: flutter_contacts: ^1.1.9+2 maplibre_gl: ^0.25.0 + # calendar sync + device_calendar: ^4.3.3 + # helpers device_info_plus: ^12.1.0 flutter_secure_storage: ^10.0.0 @@ -92,6 +95,7 @@ dependency_overrides: url: https://github.com/jakobkoerber/flutter_linkify ref: master meta: ^1.17.0 + timezone: ^0.10.0 dev_dependencies: # code generation From f7ca9adf55ef49b10d96bb23535c7ee054c74582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20M=C3=BChlberger?= Date: Tue, 14 Apr 2026 10:09:07 +0200 Subject: [PATCH 2/3] feat: mirror TUM calendar events to device calendar - add CalendarSyncService for one-way export to a device calendar - register and trigger calendar mirroring from CalendarViewModel - add calendar sync preference support - prevent duplicate event creation by guarding repeated save taps - skip canceled events when exporting to the device calendar --- lib/base/enums/user_preference.dart | 1 + .../services/calendar_sync_service.dart | 211 ++++++++++++++++++ .../calendar_addition_viewmodel.dart | 7 + .../viewModels/calendar_viewmodel.dart | 34 +++ lib/main.dart | 4 + 5 files changed, 257 insertions(+) create mode 100644 lib/calendarComponent/services/calendar_sync_service.dart diff --git a/lib/base/enums/user_preference.dart b/lib/base/enums/user_preference.dart index 7ec6e0d4..963a6448 100644 --- a/lib/base/enums/user_preference.dart +++ b/lib/base/enums/user_preference.dart @@ -10,6 +10,7 @@ enum UserPreference { failedGrades(bool), weekends(bool), hiddenCalendarEntries(bool), + calendarSync(bool), calendarTab(int); final Type type; diff --git a/lib/calendarComponent/services/calendar_sync_service.dart b/lib/calendarComponent/services/calendar_sync_service.dart new file mode 100644 index 00000000..57d65112 --- /dev/null +++ b/lib/calendarComponent/services/calendar_sync_service.dart @@ -0,0 +1,211 @@ +import 'package:campus_flutter/calendarComponent/model/calendar_event.dart'; +import 'package:device_calendar/device_calendar.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class CalendarSyncService { + static const _calendarName = "TUM Campus"; + static const _syncMappingKey = "calendar_sync_mapping"; + static const _syncCalendarIdKey = "calendar_sync_calendar_id"; + + final DeviceCalendarPlugin _deviceCalendar; + final SharedPreferences _sharedPreferences; + + CalendarSyncService(this._sharedPreferences) + : _deviceCalendar = DeviceCalendarPlugin(); + + /// Requests calendar permissions from the user. + /// Returns true if permissions were granted. + Future requestPermissions() async { + var permissionsGranted = await _deviceCalendar.hasPermissions(); + if (permissionsGranted.isSuccess && !(permissionsGranted.data ?? false)) { + permissionsGranted = await _deviceCalendar.requestPermissions(); + } + return permissionsGranted.isSuccess && (permissionsGranted.data ?? false); + } + + /// Checks if calendar permissions are currently granted. + Future hasPermissions() async { + final result = await _deviceCalendar.hasPermissions(); + return result.isSuccess && (result.data ?? false); + } + + /// Gets or creates the TUM calendar on the device. + /// Returns the calendar ID or null if it could not be created. + Future getOrCreateTumCalendar() async { + // Check if we have a stored calendar ID that still exists + final storedId = _sharedPreferences.getString(_syncCalendarIdKey); + if (storedId != null) { + final calendarsResult = await _deviceCalendar.retrieveCalendars(); + if (calendarsResult.isSuccess && calendarsResult.data != null) { + final exists = calendarsResult.data!.any((c) => c.id == storedId); + if (exists) return storedId; + } + } + + // Try to find an existing TUM calendar + final calendarsResult = await _deviceCalendar.retrieveCalendars(); + if (calendarsResult.isSuccess && calendarsResult.data != null) { + for (final calendar in calendarsResult.data!) { + if (calendar.name == _calendarName) { + _sharedPreferences.setString(_syncCalendarIdKey, calendar.id!); + return calendar.id; + } + } + } + + // Create a new calendar + // Note: device_calendar createCalendar may not be available on all platforms + // On iOS, it creates a local calendar. On Android, it creates under the default account. + try { + final result = await _deviceCalendar.createCalendar( + _calendarName, + localAccountName: _calendarName, + ); + if (result.isSuccess && result.data != null) { + _sharedPreferences.setString(_syncCalendarIdKey, result.data!); + return result.data; + } + } catch (_) {} + + return null; + } + + /// Syncs the provided TUM calendar events to the device calendar. + /// Only syncs visible, non-canceled events. + Future syncEvents(List events) async { + final hasPerms = await hasPermissions(); + if (!hasPerms) { + return SyncResult( + synced: 0, + failed: 0, + deleted: 0, + error: "No calendar permissions", + ); + } + + final calendarId = await getOrCreateTumCalendar(); + if (calendarId == null) { + return SyncResult( + synced: 0, + failed: 0, + deleted: 0, + error: "Could not create TUM calendar", + ); + } + + final mapping = _loadSyncMapping(); + final visibleEvents = events + .where((e) => (e.isVisible ?? true) && !e.isCanceled) + .toList(); + final currentTumEventIds = visibleEvents.map((e) => e.id).toSet(); + + int synced = 0; + int failed = 0; + int deleted = 0; + + // Add or update events + for (final tumEvent in visibleEvents) { + try { + final deviceEventId = mapping[tumEvent.id]; + final event = _mapToDeviceEvent(calendarId, tumEvent, deviceEventId); + + final result = await _deviceCalendar.createOrUpdateEvent(event); + if (result?.isSuccess == true && result?.data != null) { + mapping[tumEvent.id] = result!.data!; + synced++; + } else { + failed++; + } + } catch (_) { + failed++; + } + } + + // Delete events that are no longer in the TUM calendar + final idsToRemove = []; + for (final entry in mapping.entries) { + if (!currentTumEventIds.contains(entry.key)) { + try { + await _deviceCalendar.deleteEvent(calendarId, entry.value); + deleted++; + } catch (_) {} + idsToRemove.add(entry.key); + } + } + for (final id in idsToRemove) { + mapping.remove(id); + } + + _saveSyncMapping(mapping); + + return SyncResult(synced: synced, failed: failed, deleted: deleted); + } + + /// Removes the TUM calendar and all synced events from the device. + Future removeSyncedCalendar() async { + final calendarId = _sharedPreferences.getString(_syncCalendarIdKey); + if (calendarId != null) { + try { + await _deviceCalendar.deleteCalendar(calendarId); + } catch (_) {} + } + _sharedPreferences.remove(_syncCalendarIdKey); + _sharedPreferences.remove(_syncMappingKey); + } + + Event _mapToDeviceEvent( + String calendarId, + CalendarEvent tumEvent, + String? existingDeviceEventId, + ) { + final local = getLocation('Europe/Berlin'); + final event = Event( + calendarId, + eventId: existingDeviceEventId, + title: tumEvent.title ?? "", + start: TZDateTime.from(tumEvent.startDate, local), + end: TZDateTime.from(tumEvent.endDate, local), + description: tumEvent.description, + location: tumEvent.locations.isNotEmpty + ? tumEvent.locations.join(", ") + : null, + ); + return event; + } + + /// Loads the TUM event ID → device calendar event ID mapping. + Map _loadSyncMapping() { + final raw = _sharedPreferences.getStringList(_syncMappingKey); + if (raw == null) return {}; + final map = {}; + for (final entry in raw) { + final parts = entry.split("="); + if (parts.length == 2) { + map[parts[0]] = parts[1]; + } + } + return map; + } + + /// Saves the mapping as a list of "tumId=deviceId" strings. + void _saveSyncMapping(Map mapping) { + final list = mapping.entries.map((e) => "${e.key}=${e.value}").toList(); + _sharedPreferences.setStringList(_syncMappingKey, list); + } +} + +class SyncResult { + final int synced; + final int failed; + final int deleted; + final String? error; + + SyncResult({ + required this.synced, + required this.failed, + required this.deleted, + this.error, + }); + + bool get hasError => error != null; +} diff --git a/lib/calendarComponent/viewModels/calendar_addition_viewmodel.dart b/lib/calendarComponent/viewModels/calendar_addition_viewmodel.dart index b4f98caf..915643fc 100644 --- a/lib/calendarComponent/viewModels/calendar_addition_viewmodel.dart +++ b/lib/calendarComponent/viewModels/calendar_addition_viewmodel.dart @@ -26,6 +26,7 @@ class CalendarAdditionViewModel { String? id; final Ref ref; + bool _isSaving = false; CalendarAdditionViewModel(this.ref) { final date = ref.read(selectedDate); @@ -114,6 +115,9 @@ class CalendarAdditionViewModel { } Future saveEvent() async { + if (_isSaving) return; + _isSaving = true; + try { if (id != null) { await CalendarService.deleteCalendarEvent(id!); } @@ -149,6 +153,9 @@ class CalendarAdditionViewModel { ), ); } + } finally { + _isSaving = false; + } } void checkValidity() { diff --git a/lib/calendarComponent/viewModels/calendar_viewmodel.dart b/lib/calendarComponent/viewModels/calendar_viewmodel.dart index fd57a588..25798ea7 100644 --- a/lib/calendarComponent/viewModels/calendar_viewmodel.dart +++ b/lib/calendarComponent/viewModels/calendar_viewmodel.dart @@ -4,6 +4,7 @@ import 'package:campus_flutter/base/enums/user_preference.dart'; import 'package:campus_flutter/calendarComponent/model/calendar_event.dart'; import 'package:campus_flutter/calendarComponent/services/calendar_preference_service.dart'; import 'package:campus_flutter/calendarComponent/services/calendar_service.dart'; +import 'package:campus_flutter/calendarComponent/services/calendar_sync_service.dart'; import 'package:campus_flutter/main.dart'; import 'package:campus_flutter/base/services/user_preferences_service.dart'; import 'package:flutter/material.dart'; @@ -41,6 +42,7 @@ class CalendarViewModel { } events.add(response.$2); updateHomeWidget(response.$2); + _syncToDeviceCalendar(response.$2); }, onError: (error) => events.addError(error)); } @@ -181,4 +183,36 @@ class CalendarViewModel { events.add(elements); updateHomeWidget(events.value ?? []); } + + Future _syncToDeviceCalendar(List calendarEvents) async { + final isSyncEnabled = + getIt().load(UserPreference.calendarSync) + as bool? ?? + false; + if (!isSyncEnabled) return; + + final syncService = getIt(); + await syncService.syncEvents(calendarEvents); + } + + Future enableCalendarSync() async { + final syncService = getIt(); + final granted = await syncService.requestPermissions(); + if (!granted) return false; + + getIt().save(UserPreference.calendarSync, true); + + // Trigger an initial sync with current events + final currentEvents = events.value; + if (currentEvents != null) { + await syncService.syncEvents(currentEvents); + } + return true; + } + + Future disableCalendarSync() async { + getIt().save(UserPreference.calendarSync, false); + final syncService = getIt(); + await syncService.removeSyncedCalendar(); + } } diff --git a/lib/main.dart b/lib/main.dart index 8742a4a6..33f5a73d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/base/theme/dark_theme.dart'; import 'package:campus_flutter/base/theme/light_theme.dart'; import 'package:campus_flutter/calendarComponent/services/calendar_preference_service.dart'; +import 'package:campus_flutter/calendarComponent/services/calendar_sync_service.dart'; import 'package:campus_flutter/calendarComponent/services/calendar_view_service.dart'; import 'package:campus_flutter/onboardingComponent/services/onboarding_service.dart'; import 'package:campus_flutter/navigation_service.dart'; @@ -109,6 +110,9 @@ Future _initializeServices() async { getIt.registerSingleton( CalendarPreferenceService(sharedPreferences), ); + getIt.registerSingleton( + CalendarSyncService(sharedPreferences), + ); } Future _initializeHomeWidgets() async { From d05cede5db9049f71b895f635b54ecceb531f4d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20M=C3=BChlberger?= Date: Tue, 14 Apr 2026 10:09:14 +0200 Subject: [PATCH 3/3] feat: add calendar export controls in settings - add settings state for device calendar export - replace sync wording with export wording - add confirmation dialogs explaining one-way export behavior - add English and German translations for the new copy --- assets/translations/de.json | 6 ++ assets/translations/en.json | 6 ++ .../viewModels/settings_viewmodel.dart | 4 + .../views/calendar_settings_view.dart | 87 +++++++++++++++++++ .../views/settings_view.dart | 1 + 5 files changed, 104 insertions(+) diff --git a/assets/translations/de.json b/assets/translations/de.json index c618d91e..210539ce 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -205,6 +205,12 @@ "unknownDirection": "Unbekannte Richtung", "showWeekends": "Wochenenden zeigen", "showHiddenCalendarEntries": "Versteckte Kalendareintrage zeigen", + "exportToDeviceCalendar": "In Gerätekalender exportieren", + "exportCalendarDescription": "Deine TUM-Kalendereinträge werden in einen \"TUM Campus\"-Kalender auf deinem Gerät kopiert. Die Einträge werden bei jedem App-Start aktualisiert. Änderungen im Gerätekalender werden nicht zu TUM zurückübertragen.", + "removeExportedCalendar": "Exportierten Kalender entfernen", + "removeExportedCalendarDescription": "Der \"TUM Campus\"-Kalender und alle seine Einträge werden von deinem Gerät entfernt.", + "enable": "Aktivieren", + "remove": "Entfernen", "color": "Farbe", "resetLogin": "Zurücksetzen & Anmelden", "resetPreferences": "Einstellungen zurücksetzen", diff --git a/assets/translations/en.json b/assets/translations/en.json index ab1e064c..c1c49833 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -205,6 +205,12 @@ "unknownDirection": "Unknown Direction", "showWeekends": "Show Weekends", "showHiddenCalendarEntries": "Show Hidden Calendar Entries", + "exportToDeviceCalendar": "Export to Device Calendar", + "exportCalendarDescription": "Your TUM calendar events will be copied to a \"TUM Campus\" calendar on your device. Events are updated each time the app is opened. This does not sync changes made on your device back to TUM.", + "removeExportedCalendar": "Remove Exported Calendar", + "removeExportedCalendarDescription": "This will remove the \"TUM Campus\" calendar and all its events from your device.", + "enable": "Enable", + "remove": "Remove", "color": "Color", "resetLogin": "Reset & Login", "resetPreferences": "Reset Preferences", diff --git a/lib/settingsComponent/viewModels/settings_viewmodel.dart b/lib/settingsComponent/viewModels/settings_viewmodel.dart index 02dd32e4..8b196473 100644 --- a/lib/settingsComponent/viewModels/settings_viewmodel.dart +++ b/lib/settingsComponent/viewModels/settings_viewmodel.dart @@ -37,6 +37,8 @@ class SettingsViewModel { case UserPreference.hiddenCalendarEntries: ref.read(showHiddenCalendarEntries.notifier).state = value as bool; + case UserPreference.calendarSync: + ref.read(syncCalendarWithDevice.notifier).state = value as bool; default: break; } @@ -61,6 +63,8 @@ class SettingsViewModel { ref.read(showWeekends.notifier).state = value as bool; case UserPreference.hiddenCalendarEntries: ref.read(showHiddenCalendarEntries.notifier).state = value as bool; + case UserPreference.calendarSync: + ref.read(syncCalendarWithDevice.notifier).state = value as bool; default: break; } diff --git a/lib/settingsComponent/views/calendar_settings_view.dart b/lib/settingsComponent/views/calendar_settings_view.dart index 69eaafb3..1b30fb4d 100644 --- a/lib/settingsComponent/views/calendar_settings_view.dart +++ b/lib/settingsComponent/views/calendar_settings_view.dart @@ -22,6 +22,7 @@ class CalendarSettingsView extends ConsumerWidget { widgets: [ _showWeekends(context, ref), _showHiddenCalendarEntries(context, ref), + _syncWithDeviceCalendar(context, ref), ], ), ), @@ -67,4 +68,90 @@ class CalendarSettingsView extends ConsumerWidget { ), ); } + + Widget _syncWithDeviceCalendar(BuildContext context, WidgetRef ref) { + return ListTile( + dense: true, + title: Text( + context.tr("exportToDeviceCalendar"), + style: Theme.of(context).textTheme.bodyMedium, + ), + trailing: Switch( + value: ref.watch(syncCalendarWithDevice), + onChanged: (value) async { + if (value) { + _showEnableExportDialog(context, ref); + } else { + _showDisableExportDialog(context, ref); + } + }, + ), + ); + } + + void _showEnableExportDialog(BuildContext context, WidgetRef ref) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text( + context.tr("exportToDeviceCalendar"), + textAlign: TextAlign.center, + ), + content: Text( + context.tr("exportCalendarDescription"), + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(context.tr("cancel")), + ), + ElevatedButton( + onPressed: () async { + Navigator.of(context).pop(); + final success = + await ref.read(calendarViewModel).enableCalendarSync(); + if (success) { + ref.read(syncCalendarWithDevice.notifier).state = true; + } + }, + child: Text(context.tr("enable")), + ), + ], + ), + ); + } + + void _showDisableExportDialog(BuildContext context, WidgetRef ref) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text( + context.tr("removeExportedCalendar"), + textAlign: TextAlign.center, + ), + content: Text( + context.tr("removeExportedCalendarDescription"), + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(context.tr("cancel")), + ), + ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(Colors.redAccent), + ), + onPressed: () async { + Navigator.of(context).pop(); + await ref.read(calendarViewModel).disableCalendarSync(); + ref.read(syncCalendarWithDevice.notifier).state = false; + }, + child: Text(context.tr("remove")), + ), + ], + ), + ); + } } diff --git a/lib/settingsComponent/views/settings_view.dart b/lib/settingsComponent/views/settings_view.dart index 4f98f399..d106a2ff 100644 --- a/lib/settingsComponent/views/settings_view.dart +++ b/lib/settingsComponent/views/settings_view.dart @@ -19,6 +19,7 @@ final showStudentCardPicture = StateProvider((ref) => true); final hideFailedGrades = StateProvider((ref) => false); final showWeekends = StateProvider((ref) => false); final showHiddenCalendarEntries = StateProvider((ref) => false); +final syncCalendarWithDevice = StateProvider((ref) => false); class SettingsView extends ConsumerWidget { const SettingsView({super.key});