From 991ee57a2fda38575d684bf78d9c88f2f5212ee0 Mon Sep 17 00:00:00 2001 From: Jonas Richard Richter Date: Tue, 27 Sep 2022 10:46:09 +0200 Subject: [PATCH] 6 synchronization of lessons with the system calendar (#33) * Add base views for calendar export * Add first working concept version of the export * Begin rework of navigation structure * Remove about section from settings * Remove TabView for main navigation * Add calendar export to onboarding * Fix multiple UI bugs * Add multiple improvements * Add multiple improvements to error handling * Add confirmation alert for calendar export * Add error dialog for calendar export * Fix Localization bug * Add InfoPlist local * Fix localiaztion * Fix visual bug --- BA-Schedule.xcodeproj/project.pbxproj | 48 ++++- BA-Schedule/Info.plist | 2 + .../Resources/de.lproj/Localizable.strings | 24 ++- .../Resources/en.lproj/Localizable.strings | 22 ++- BA-Schedule/Sources/ContentView.swift | 17 +- .../Sources/Extensions/URL+BA-Schedule.swift | 2 +- .../Sources/UI Elements/ErrorView.swift | 2 + BA-Schedule/Sources/UI Elements/Lesson.swift | 5 +- .../CalendarExport/ExportToCalendarView.swift | 187 ++++++++++++++++++ .../GrantCalendarPermissionView.swift | 75 +++++++ .../NoCalendarPermissionView.swift | 51 +++++ .../Sources/Views/Onboarding/LoginView.swift | 22 +-- .../Views/Onboarding/OnboardingView.swift | 16 +- .../Sources/Views/Schedule/ScheduleView.swift | 78 +++++++- .../Sources/Views/Settings/AboutView.swift | 100 ++++++---- .../Sources/Views/Settings/SettingsView.swift | 143 -------------- BA-Schedule/de.lproj/InfoPlist.strings | 8 + BA-Schedule/en.lproj/InfoPlist.strings | 8 + 18 files changed, 577 insertions(+), 233 deletions(-) create mode 100644 BA-Schedule/Sources/Views/CalendarExport/ExportToCalendarView.swift create mode 100644 BA-Schedule/Sources/Views/CalendarExport/GrantCalendarPermissionView.swift create mode 100644 BA-Schedule/Sources/Views/CalendarExport/NoCalendarPermissionView.swift delete mode 100644 BA-Schedule/Sources/Views/Settings/SettingsView.swift create mode 100644 BA-Schedule/de.lproj/InfoPlist.strings create mode 100644 BA-Schedule/en.lproj/InfoPlist.strings diff --git a/BA-Schedule.xcodeproj/project.pbxproj b/BA-Schedule.xcodeproj/project.pbxproj index 5962816..1716504 100644 --- a/BA-Schedule.xcodeproj/project.pbxproj +++ b/BA-Schedule.xcodeproj/project.pbxproj @@ -7,8 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 4100A29228E244DD0076CFC5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4100A29428E244DD0076CFC5 /* InfoPlist.strings */; }; + 4108E52228B614960037BA44 /* NoCalendarPermissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4108E52128B614960037BA44 /* NoCalendarPermissionView.swift */; }; 4111ED0427B4451A00C7FF8D /* Binding+not.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4111ED0327B4451900C7FF8D /* Binding+not.swift */; }; 4129F38C2798B81100A46155 /* Logger+CustomInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4129F38B2798B81100A46155 /* Logger+CustomInit.swift */; }; + 4135B66628B414110008D573 /* ExportToCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4135B66528B414110008D573 /* ExportToCalendarView.swift */; }; 41407EED27A9BC9E00DBB55A /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41407EEC27A9BC9E00DBB55A /* ServiceWrapper.swift */; }; 41407EEF27A9C31600DBB55A /* StudyDay+AppReviewDemoData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41407EEE27A9C31600DBB55A /* StudyDay+AppReviewDemoData.swift */; }; 4153B45C28290495001E28E6 /* AdaptiveStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4153B45B28290495001E28E6 /* AdaptiveStack.swift */; }; @@ -18,7 +21,6 @@ 416306B327A6A11800A92714 /* Lesson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416306B227A6A11800A92714 /* Lesson.swift */; }; 416306B927A6BC3000A92714 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 416306BB27A6BC3000A92714 /* Localizable.strings */; }; 416306BE27A6BCBE00A92714 /* ScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416306BD27A6BCBE00A92714 /* ScheduleView.swift */; }; - 416306C227A6BE0100A92714 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416306C127A6BE0100A92714 /* SettingsView.swift */; }; 416306C427A6BE4800A92714 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416306C327A6BE4800A92714 /* OnboardingView.swift */; }; 416306C627A6C25500A92714 /* Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416306C527A6C25500A92714 /* Feature.swift */; }; 416306C827A6D67800A92714 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416306C727A6D67700A92714 /* Settings.swift */; }; @@ -32,13 +34,18 @@ 41A8BC3D2798B1FB0042DBB9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41A8BC3C2798B1FB0042DBB9 /* Assets.xcassets */; }; 41A8BC402798B1FB0042DBB9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41A8BC3F2798B1FB0042DBB9 /* Preview Assets.xcassets */; }; 41A8BC4B2798B7210042DBB9 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 41A8BC4A2798B7210042DBB9 /* Logging */; }; + 41C1556428B3D63F00977721 /* GrantCalendarPermissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41C1556328B3D63F00977721 /* GrantCalendarPermissionView.swift */; }; 41F1A5F928202ED50039F6D6 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 41F1A5F828202ED50039F6D6 /* Settings.bundle */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 4100A29328E244DD0076CFC5 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; + 4100A29528E244E10076CFC5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 4108E52128B614960037BA44 /* NoCalendarPermissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCalendarPermissionView.swift; sourceTree = ""; }; 4111ED0327B4451900C7FF8D /* Binding+not.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+not.swift"; sourceTree = ""; }; 4129F38B2798B81100A46155 /* Logger+CustomInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+CustomInit.swift"; sourceTree = ""; }; 412FB47C27B57D8900660427 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 4135B66528B414110008D573 /* ExportToCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportToCalendarView.swift; sourceTree = ""; }; 41407EEC27A9BC9E00DBB55A /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = ""; }; 41407EEE27A9C31600DBB55A /* StudyDay+AppReviewDemoData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StudyDay+AppReviewDemoData.swift"; sourceTree = ""; }; 4153B45B28290495001E28E6 /* AdaptiveStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveStack.swift; sourceTree = ""; }; @@ -48,7 +55,6 @@ 416306B227A6A11800A92714 /* Lesson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lesson.swift; sourceTree = ""; }; 416306BA27A6BC3000A92714 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 416306BD27A6BCBE00A92714 /* ScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleView.swift; sourceTree = ""; }; - 416306C127A6BE0100A92714 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 416306C327A6BE4800A92714 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; 416306C527A6C25500A92714 /* Feature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feature.swift; sourceTree = ""; }; 416306C727A6D67700A92714 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; @@ -62,6 +68,7 @@ 41A8BC3A2798B1FA0042DBB9 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 41A8BC3C2798B1FB0042DBB9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41A8BC3F2798B1FB0042DBB9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 41C1556328B3D63F00977721 /* GrantCalendarPermissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrantCalendarPermissionView.swift; sourceTree = ""; }; 41ED2F11281FB8A900FA12C8 /* CampusDualKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CampusDualKit; path = Packages/CampusDualKit; sourceTree = ""; }; 41F1A5F828202ED50039F6D6 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Settings.bundle; path = "BA-Schedule/Settings.bundle"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -93,12 +100,12 @@ 41407EE727A9B8E400DBB55A /* Sources */ = { isa = PBXGroup; children = ( - 41407EEB27A9BC8E00DBB55A /* Services */, + 41A8BC382798B1FA0042DBB9 /* BA_ScheduleApp.swift */, + 41A8BC3A2798B1FA0042DBB9 /* ContentView.swift */, 41A8BC482798B6EE0042DBB9 /* Extensions */, + 41407EEB27A9BC8E00DBB55A /* Services */, 41A8BC472798B6D60042DBB9 /* UI Elements */, 41A8BC462798B6CD0042DBB9 /* Views */, - 41A8BC382798B1FA0042DBB9 /* BA_ScheduleApp.swift */, - 41A8BC3A2798B1FA0042DBB9 /* ContentView.swift */, ); path = Sources; sourceTree = ""; @@ -134,7 +141,6 @@ isa = PBXGroup; children = ( 41A437F227A6FEE700D0D43B /* AboutView.swift */, - 416306C127A6BE0100A92714 /* SettingsView.swift */, 416306C727A6D67700A92714 /* Settings.swift */, ); path = Settings; @@ -179,9 +185,10 @@ isa = PBXGroup; children = ( 419408C3281FD32100C60B9F /* BA-Schedule.entitlements */, + 4100A29428E244DD0076CFC5 /* InfoPlist.strings */, 412FB47C27B57D8900660427 /* Info.plist */, - 41407EE727A9B8E400DBB55A /* Sources */, 41407EE627A9B8DA00DBB55A /* Resources */, + 41407EE727A9B8E400DBB55A /* Sources */, ); path = "BA-Schedule"; sourceTree = ""; @@ -197,10 +204,11 @@ 41A8BC462798B6CD0042DBB9 /* Views */ = { isa = PBXGroup; children = ( + 41C1556228B3D62A00977721 /* CalendarExport */, 4153B45A28290485001E28E6 /* General */, 416306C027A6BDF300A92714 /* Onboarding */, - 416306BF27A6BDEE00A92714 /* Settings */, 416306BC27A6BCAA00A92714 /* Schedule */, + 416306BF27A6BDEE00A92714 /* Settings */, ); path = Views; sourceTree = ""; @@ -225,6 +233,16 @@ path = Extensions; sourceTree = ""; }; + 41C1556228B3D62A00977721 /* CalendarExport */ = { + isa = PBXGroup; + children = ( + 41C1556328B3D63F00977721 /* GrantCalendarPermissionView.swift */, + 4135B66528B414110008D573 /* ExportToCalendarView.swift */, + 4108E52128B614960037BA44 /* NoCalendarPermissionView.swift */, + ); + path = CalendarExport; + sourceTree = ""; + }; 41ED2F10281FB8A900FA12C8 /* Packages */ = { isa = PBXGroup; children = ( @@ -306,6 +324,7 @@ 41A8BC402798B1FB0042DBB9 /* Preview Assets.xcassets in Resources */, 416306B927A6BC3000A92714 /* Localizable.strings in Resources */, 41A8BC3D2798B1FB0042DBB9 /* Assets.xcassets in Resources */, + 4100A29228E244DD0076CFC5 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -340,17 +359,19 @@ 416306BE27A6BCBE00A92714 /* ScheduleView.swift in Sources */, 41A8BC3B2798B1FA0042DBB9 /* ContentView.swift in Sources */, 41A8BC392798B1FA0042DBB9 /* BA_ScheduleApp.swift in Sources */, - 416306C227A6BE0100A92714 /* SettingsView.swift in Sources */, + 4135B66628B414110008D573 /* ExportToCalendarView.swift in Sources */, 41407EEF27A9C31600DBB55A /* StudyDay+AppReviewDemoData.swift in Sources */, 4154BA26281969CA00907AD5 /* ScheduleListView.swift in Sources */, 41A437F327A6FEE700D0D43B /* AboutView.swift in Sources */, 415FEC0B281FE62000D74DE9 /* ErrorView.swift in Sources */, 416306C627A6C25500A92714 /* Feature.swift in Sources */, 416306CD27A6DC7100A92714 /* URL+BA-Schedule.swift in Sources */, + 41C1556428B3D63F00977721 /* GrantCalendarPermissionView.swift in Sources */, 4129F38C2798B81100A46155 /* Logger+CustomInit.swift in Sources */, 41407EED27A9BC9E00DBB55A /* ServiceWrapper.swift in Sources */, 416306C827A6D67800A92714 /* Settings.swift in Sources */, 4111ED0427B4451A00C7FF8D /* Binding+not.swift in Sources */, + 4108E52228B614960037BA44 /* NoCalendarPermissionView.swift in Sources */, 4153B45C28290495001E28E6 /* AdaptiveStack.swift in Sources */, 4154BA2A2819786600907AD5 /* ScheduleAppError.swift in Sources */, 416306CF27A6DD9600A92714 /* LoginView.swift in Sources */, @@ -362,6 +383,15 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ + 4100A29428E244DD0076CFC5 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 4100A29328E244DD0076CFC5 /* de */, + 4100A29528E244E10076CFC5 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; 416306BB27A6BC3000A92714 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/BA-Schedule/Info.plist b/BA-Schedule/Info.plist index 669a5ca..4061f89 100644 --- a/BA-Schedule/Info.plist +++ b/BA-Schedule/Info.plist @@ -2,6 +2,8 @@ + NSCalendarsUsageDescription + BA-Schedule benötigt Kalenderzugriff um deinen Stundenplan zu exportieren. ITSAppUsesNonExemptEncryption UIApplicationSceneManifest diff --git a/BA-Schedule/Resources/de.lproj/Localizable.strings b/BA-Schedule/Resources/de.lproj/Localizable.strings index 55aea46..65446c6 100644 --- a/BA-Schedule/Resources/de.lproj/Localizable.strings +++ b/BA-Schedule/Resources/de.lproj/Localizable.strings @@ -26,6 +26,8 @@ "FEATURE_1_DESCR" = "Dein Stundenplan ganz einfach, ordentlich und mobil verfügbar."; "FEATURE_2_TITLE" = "Auslegung auf Datenschutz"; "FEATURE_2_DESCR" = "Die App wurde mit einem Fokus auf Datenschutz entwickelt."; +"FEATURE_CALENDAR_EXPORT" = "Einfacher Export des Stundenplan"; +"FEATURE_CALENDAR_EXPORT_DESCR" = "Exportiere deinen Studenplan mit einem Klick in deinen eigenen Kalender."; // MARK: - Settings "SETTINGS.GITHUB-LINK" = "Zum Quelltext auf GitHub"; @@ -39,10 +41,14 @@ "SETTINGS.MOREFUNCTIONS.SHOWINSTRUCTUR" = "Lehrer:in anzeigen"; "SETTINGS.MOREFUNCTIONS.SHOWREMARKS" = "Bemerkungen anzeigen"; "SETTINGS.IDEAS" = "Ideen für weitere Funktionen?"; -"SETTINGS.IDEAS.BUTTON" = "Vorschlag per E-Mail senden"; +"SETTINGS.IDEAS.BUTTON" = "Funktionsvorschlag per E-Mail senden"; + +// MARK: - About +"ABOUT.THANKSTO" = "Danke für die Mithilfe an"; // MARK: - Errors "ALERT_BUTTON_CANCEL" = "Abbrechen"; +"ERROR_ALERT" = "Es ist ein Fehler aufgetreten."; // MARK: - General "GENERAL.UNKNOWN" = "Unbekannt"; @@ -52,3 +58,19 @@ "GENERAL.TRYAGAIN" = "Erneut versuchen"; "GENERAL.NOCONNECTION.TITLE" = "Keine Netzwerkverbindung"; "GENERAL.NOCONNECTION.MESSAGE" = "Bitte überprüfe deine Netzwerkverbindung und versuche es später erneut."; +"GENERAL_MENU_MORE" = "Weitere Inhalte"; +"GENERAL_CLOSE" = "Schließen"; +"GENERAL_FINISH" = "Fertig"; + + + +// MARK: - Calendar Export +"PERMISSON.CALENDAR.TITLE" = "Kalendarzugriff benötigt"; +"PERMISSON.CALENDAR.DESCR" = "Um deinen Stundenplan in deinen Kalendar zu übertragen ist deine Zustimmung erforderlich. Der Stundenplan wird in einem neuen Kalendar angelegt. Es werden **keine** anderen Termine ausgelesen."; +"ERROR_NO_CALENDAR_ACCESS" = "Kalendarzugriff nicht möglich"; +"ERROR_NO_CALENDAR_ACCESS_DESCR" = "Anscheinend wurden der App nicht die ausreichenden Berechtigungen gestattet. Um den Zugriff auf den Kalendar zu erlauben, wechsle bitte in die Einstellungen."; +"ERROR_NO_CALENDAR_ACCESS_TO_SETTINGS" = "Zu den Einstellungen"; +"CALENDAR_EXPORT_SUCCESS" = "Der Kalendar wurde erfolgreich exportiert"; +"CALENDAR_EXPORT_TITLE" = "Studenplan in den Kalendar exportieren"; +"CALENDAR_EXPORT_BUTTON" = "Exportieren"; +"CALENDAR_EXPORT_MENU" = "Stundenplan exportieren"; diff --git a/BA-Schedule/Resources/en.lproj/Localizable.strings b/BA-Schedule/Resources/en.lproj/Localizable.strings index fa6af43..ed813d4 100644 --- a/BA-Schedule/Resources/en.lproj/Localizable.strings +++ b/BA-Schedule/Resources/en.lproj/Localizable.strings @@ -26,6 +26,8 @@ "FEATURE_1_DESCR" = "Your schedule is easy, neat and available on the go."; "FEATURE_2_TITLE" = "Focused on privacy"; "FEATURE_2_DESCR" = "The app was developed with privacy in mind."; +"FEATURE_CALENDAR_EXPORT" = "Easy schedule export"; +"FEATURE_CALENDAR_EXPORT_DESCR" = "Export your schedule to your personal calendar."; // MARK: - Settings "SETTINGS.GITHUB-LINK" = "See the source code on GitHub"; @@ -39,10 +41,14 @@ "SETTINGS.MOREFUNCTIONS.SHOWINSTRUCTUR" = "Show instructor"; "SETTINGS.MOREFUNCTIONS.SHOWREMARKS" = "Show remarks"; "SETTINGS.IDEAS" = "Ideas for more features?"; -"SETTINGS.IDEAS.BUTTON" = "Send proposal via email"; +"SETTINGS.IDEAS.BUTTON" = "Send feature proposal via email"; + +// MARK: - About +"ABOUT.THANKSTO" = "Thanks for helping out"; // MARK: - Errors "ALERT_BUTTON_CANCEL" = "Cancel"; +"ERROR_ALERT" = "An error happend"; // MARK: - General "GENERAL.UNKNOWN" = "Unknown"; @@ -52,3 +58,17 @@ "GENERAL.CONTINUE" = "Continue"; "GENERAL.NOCONNECTION.TITLE" = "No network connection"; "GENERAL.NOCONNECTION.MESSAGE" = "Please check your network connection and try again later."; +"GENERAL_MENU_MORE" = "More content"; +"GENERAL_CLOSE" = "Close"; +"GENERAL_FINISH" = "Finish"; + +// MARK: - Calendar export +"PERMISSON.CALENDAR.TITLE" = "Calendar access required"; +"PERMISSON.CALENDAR.DESCR" = "In order to transfer your timetable to your calendar, your consent is required. The timetable is created in a new calendar. **No** other dates will be read out."; +"ERROR_NO_CALENDAR_ACCESS" = "Calendar access not possible"; +"ERROR_NO_CALENDAR_ACCESS_DESCR" = "Apparently, the app was not allowed sufficient permissions. To allow access to the calendar, please switch to the settings."; +"ERROR_NO_CALENDAR_ACCESS_TO_SETTINGS" = "To the settings"; +"CALENDAR_EXPORT_SUCCESS" = "The calendar export was successfull"; +"CALENDAR_EXPORT_TITLE" = "Export schedule into your calendar"; +"CALENDAR_EXPORT_BUTTON" = "Export"; +"CALENDAR_EXPORT_MENU" = "Export schedule"; diff --git a/BA-Schedule/Sources/ContentView.swift b/BA-Schedule/Sources/ContentView.swift index b29fbce..3397fa4 100644 --- a/BA-Schedule/Sources/ContentView.swift +++ b/BA-Schedule/Sources/ContentView.swift @@ -16,23 +16,8 @@ struct ContentView: View { // MARK: - View var body: some View { - TabView { // MARK: - Schedule - ScheduleView() - .tabItem { - Image(systemName: "calendar.day.timeline.leading") - Text("SCHEDULE") - } - .navigationViewStyle(.stack) - - // MARK: - Settings - SettingsView() - .tabItem { - Image(systemName: "gear") - Text("SETTINGS") - } - .navigationViewStyle(.stack) - } + ScheduleView() // MARK: - Onboarding Sheet .sheet(isPresented: self.$settings.isOnboarded.not, content: { OnboardingView() diff --git a/BA-Schedule/Sources/Extensions/URL+BA-Schedule.swift b/BA-Schedule/Sources/Extensions/URL+BA-Schedule.swift index 035dad9..6d58b04 100644 --- a/BA-Schedule/Sources/Extensions/URL+BA-Schedule.swift +++ b/BA-Schedule/Sources/Extensions/URL+BA-Schedule.swift @@ -9,7 +9,7 @@ import Foundation internal extension URL { enum BaSchedule { - static let github = URL(string: "https://github.com/jns-rchtr/BA-Schedule/")! + static let github = URL(string: "https://github.com/jonasrichardrichter/BA-Schedule/")! static let informationHash = URL(string: "blob/main/LOGIN_INFORMATION.md", relativeTo: Self.github)! } } diff --git a/BA-Schedule/Sources/UI Elements/ErrorView.swift b/BA-Schedule/Sources/UI Elements/ErrorView.swift index 9d196cc..ce149a3 100644 --- a/BA-Schedule/Sources/UI Elements/ErrorView.swift +++ b/BA-Schedule/Sources/UI Elements/ErrorView.swift @@ -9,6 +9,7 @@ import SwiftUI struct ErrorView: View { public var systemName: String + public var color: Color? = .primary public var title: LocalizedStringKey public var message: LocalizedStringKey @@ -17,6 +18,7 @@ struct ErrorView: View { Image(systemName: systemName) .font(.system(size: 46)) .symbolRenderingMode(.hierarchical) + .foregroundColor(color) .padding() Text(title) .font(.headline) diff --git a/BA-Schedule/Sources/UI Elements/Lesson.swift b/BA-Schedule/Sources/UI Elements/Lesson.swift index e200c74..6ad6d5a 100644 --- a/BA-Schedule/Sources/UI Elements/Lesson.swift +++ b/BA-Schedule/Sources/UI Elements/Lesson.swift @@ -41,7 +41,10 @@ struct Lesson: View { Text(lesson.instructor) } } - if (!self.lesson.remarks.isEmpty && self.settings.showRemarks) { + if (!self.lesson.remarks.isEmpty) { + if self.sizeClass == .regular { + Text("・") + } HStack { Image(systemName: "info.circle") Text(lesson.remarks) diff --git a/BA-Schedule/Sources/Views/CalendarExport/ExportToCalendarView.swift b/BA-Schedule/Sources/Views/CalendarExport/ExportToCalendarView.swift new file mode 100644 index 0000000..3a07d0f --- /dev/null +++ b/BA-Schedule/Sources/Views/CalendarExport/ExportToCalendarView.swift @@ -0,0 +1,187 @@ +// +// ExportToCalendarView.swift +// BA-Schedule +// +// Created by Jonas Richard Richter on 22.08.22. +// + +import SwiftUI +import EventKit +import Logging +import UIKit +import CampusDualKit + +struct ExportToCalendarView: View { + private var logger = Logger(for: "ExportToCalendarView") + @Environment(\.presentationMode) var presentationMode + + private var ekStore = EKEventStore() + + @State private var showPermissionSheet = false + @State private var showErrorSheet = false + @State private var showSuccessAlert = false + + @State private var showErrorAlert = false + @State private var error: Error? + + var body: some View { + NavigationView { + VStack { + Spacer() + Image(systemName: "calendar.badge.plus") + .resizable() + .scaledToFit() + .frame(height: 62) + .foregroundColor(.accentColor) + Text("CALENDAR_EXPORT_TITLE") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + + Spacer() + + Button { + Task { + await exportToCalendar() + } + } label: { + Text("CALENDAR_EXPORT_BUTTON") + .bold() + .frame(height: 32) + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + + } + .padding() + .sheet(isPresented: $showPermissionSheet, onDismiss: { + checkPermission() + }) { + GrantCalendarPermissionView() + } + .sheet(isPresented: $showErrorSheet, onDismiss: { + presentationMode.wrappedValue.dismiss() + }) { + NoCalendarPermissionView() + } + .alert("CALENDAR_EXPORT_SUCCESS", isPresented: $showSuccessAlert) { + Button("GENERAL_FINISH", role: .cancel) { + presentationMode.wrappedValue.dismiss() + } + } + .alert("ERROR_ALERT", isPresented: $showErrorAlert, presenting: error) { _ in + Button("GENERAL_CANCEL", role: .cancel) { + presentationMode.wrappedValue.dismiss() + } + } message: { error in + Text(error.localizedDescription) + } + } + .onAppear { + checkPermission() + } + } + + func checkPermission() { + switch EKEventStore.authorizationStatus(for: .event) { + case .notDetermined: + showPermissionSheet = true + break + case .denied, .restricted: + showErrorSheet = true + break + default: + break + } + } + + func getBACalenderOrCreate() -> EKCalendar { + let userCalenders = ekStore.calendars(for: .event) + + let baCalenders = userCalenders.filter { calender in + return calender.title == "BA-Schedule" + } + + if (baCalenders.count == 0) { + let calendar = EKCalendar(for: .event, eventStore: ekStore) + + calendar.title = "BA-Schedule" + calendar.cgColor = UIColor(Color.red).cgColor + calendar.source = ekStore.defaultCalendarForNewEvents?.source! + + self.logger.info("Created calendar: \(calendar.debugDescription)") + + do { + try ekStore.saveCalendar(calendar, commit: true) + logger.info("Saved new calendar") + } catch { + logger.error("Error while saving new calendar: \(error.localizedDescription)") + showErrorAlert = true + self.error = error + } + + return calendar + } + + return baCalenders.first! + } + + func clearCalendarFromNow(_ calendar: EKCalendar, store: EKEventStore) throws { + logger.info("Clearing the calendar...") + let predicate = store.predicateForEvents(withStart: Date.now, end: Date.now.addingTimeInterval(31536000), calendars: [calendar]) + + let events = store.events(matching: predicate) + for event in events { + try store.remove(event, span: .thisEvent, commit: false) + } + + try store.commit() + logger.info("Calendar cleared!") + } + + func exportToCalendar() async { + let calender = getBACalenderOrCreate() + + let service = ServiceWrapper() + var schedule: [StudyDay] = [] + + do { + schedule = try await service.loadFromJson() + try clearCalendarFromNow(calender, store: ekStore) + } catch { + logger.error("An error happend: \(error.localizedDescription)") + showErrorAlert = true + self.error = error + return + } + + for studyDay in schedule { + for lesson in studyDay.lessons { + let event = EKEvent(eventStore: ekStore) + + event.calendar = calender + event.title = lesson.title + event.location = lesson.room + " " + lesson.remarks + event.startDate = lesson.start + event.endDate = lesson.end + event.availability = .busy + + do { + try ekStore.save(event, span: .thisEvent, commit: true) + showSuccessAlert = true + } catch { + logger.error("An error happend: \(error.localizedDescription)") + showErrorAlert = true + self.error = error + return + } + } + } + } +} + +struct ExportToCalendarView_Previews: PreviewProvider { + static var previews: some View { + ExportToCalendarView() + } +} diff --git a/BA-Schedule/Sources/Views/CalendarExport/GrantCalendarPermissionView.swift b/BA-Schedule/Sources/Views/CalendarExport/GrantCalendarPermissionView.swift new file mode 100644 index 0000000..4fbeedc --- /dev/null +++ b/BA-Schedule/Sources/Views/CalendarExport/GrantCalendarPermissionView.swift @@ -0,0 +1,75 @@ +// +// GrantCalendarPermissionView.swift +// BA-Schedule +// +// Created by Jonas Richard Richter on 22.08.22. +// + +import SwiftUI +import EventKit + +struct GrantCalendarPermissionView: View { + @Environment(\.presentationMode) var presentationMode + + var body: some View { + NavigationView { + VStack { + Spacer() + + VStack { + Image(systemName: "calendar.circle") + .resizable() + .scaledToFit() + .frame(height: 82) + .foregroundColor(.blue) + Text("PERMISSON.CALENDAR.TITLE") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + Text("PERMISSON.CALENDAR.DESCR") + .font(.footnote) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .padding(.top) + }.padding() + + Spacer() + Spacer() + Spacer() + + VStack { + Button { + self.requestAccess() + } label: { + Label { + Text("Zugriff erteilen") + } icon: { + Image(systemName: "checkmark.circle") + } + + } + .tint(.blue) + .buttonBorderShape(.capsule) + .buttonStyle(.borderedProminent) + } + + Spacer() + } + } + } + + func requestAccess() { + let store = EKEventStore() + + store.requestAccess(to: .event) { granted, error in + presentationMode.wrappedValue.dismiss() + } + } +} + +struct GrantCalendarPermissionView_Previews: PreviewProvider { + static var previews: some View { + GrantCalendarPermissionView() + .environment(\.locale, .init(identifier: "de")) + } +} diff --git a/BA-Schedule/Sources/Views/CalendarExport/NoCalendarPermissionView.swift b/BA-Schedule/Sources/Views/CalendarExport/NoCalendarPermissionView.swift new file mode 100644 index 0000000..80f50f1 --- /dev/null +++ b/BA-Schedule/Sources/Views/CalendarExport/NoCalendarPermissionView.swift @@ -0,0 +1,51 @@ +// +// NoCalendarPermissionView.swift +// BA-Schedule +// +// Created by Jonas Richard Richter on 24.08.22. +// + +import SwiftUI + +struct NoCalendarPermissionView: View { + @Environment(\.presentationMode) var presentationMode + + var body: some View { + NavigationView { + VStack { + Spacer() + ErrorView(systemName: "xmark.diamond", color: .red, title: "ERROR_NO_CALENDAR_ACCESS", message: "ERROR_NO_CALENDAR_ACCESS_DESCR") + Spacer() + Button { + UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) + } label: { + Label("ERROR_NO_CALENDAR_ACCESS_TO_SETTINGS", systemImage: "gear") + } + .buttonStyle(.borderedProminent) + .padding(.horizontal) + + Spacer() + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + presentationMode.wrappedValue.dismiss() + } label: { + Label("GENERAL_CLOSE", systemImage: "xmark") + } + .labelStyle(.titleOnly) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + + } + } + } + } +} + +struct NoCalendarPermissionView_Previews: PreviewProvider { + static var previews: some View { + NoCalendarPermissionView() + .environment(\.locale, .init(identifier: "de")) + } +} diff --git a/BA-Schedule/Sources/Views/Onboarding/LoginView.swift b/BA-Schedule/Sources/Views/Onboarding/LoginView.swift index 4773ab5..d88bfa3 100644 --- a/BA-Schedule/Sources/Views/Onboarding/LoginView.swift +++ b/BA-Schedule/Sources/Views/Onboarding/LoginView.swift @@ -29,7 +29,7 @@ struct LoginView: View { var body: some View { NavigationView { Form { - Section(content: { + Section { TextField("ONBOARDING.LOGIN.MATRIKEL", text: $username) .textInputAutocapitalization(.never) .textContentType(.username) @@ -38,7 +38,7 @@ struct LoginView: View { SecureField("ONBOARDING.LOGIN.HASH", text: $hash) .textInputAutocapitalization(.never) .textContentType(.password) - }, header: { + } header: { VStack { Image(systemName: "key.fill") .resizable() @@ -55,7 +55,7 @@ struct LoginView: View { } .frame(maxWidth: .infinity, alignment: .center) .padding() - }) + } Section { // MARK: - Button @@ -79,17 +79,17 @@ struct LoginView: View { } } } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Link(destination: URL.BaSchedule.informationHash, label: { + Label("More information", systemImage: "questionmark.circle") + }) + } + } .navigationBarTitleDisplayMode(.inline) - .navigationBarHidden(true) } - .toolbar(content: { - ToolbarItem(placement: .navigationBarTrailing) { - Link(destination: URL.BaSchedule.informationHash, label: { - Image(systemName: "questionmark.circle") - }) - } - }) .navigationViewStyle(.stack) + } // MARK: - Functions diff --git a/BA-Schedule/Sources/Views/Onboarding/OnboardingView.swift b/BA-Schedule/Sources/Views/Onboarding/OnboardingView.swift index 98c5ac7..3be06a9 100644 --- a/BA-Schedule/Sources/Views/Onboarding/OnboardingView.swift +++ b/BA-Schedule/Sources/Views/Onboarding/OnboardingView.swift @@ -13,7 +13,6 @@ struct OnboardingView: View { @EnvironmentObject var settings: Settings - // MARK: - View var body: some View { @@ -23,19 +22,24 @@ struct OnboardingView: View { .font(.largeTitle) .fontWeight(.bold) .padding(.horizontal) + .padding(.top, 42) Spacer() VStack(alignment: .leading, spacing: 40) { Feature(systemImage: "calendar.day.timeline.leading", iconColor: .blue, title: "FEATURE_1_TITLE", description: "FEATURE_1_DESCR") - + Feature(systemImage: "calendar.badge.plus", iconColor: .red, title: "FEATURE_CALENDAR_EXPORT", description: "FEATURE_CALENDAR_EXPORT_DESCR") Feature(systemImage: "lock.shield", iconColor: .yellow, title: "FEATURE_2_TITLE", description: "FEATURE_2_DESCR") }.padding(.horizontal, 30) Spacer() - // TODO: Add privacy informations - + // Text("Diese App wurde Open-Source entwickelt, dass heißt jede:r besitzt die Möglichkeit den Quelltext, also die Funktionsweise der App nachzuvollziehen. \nMehr erfahren...") + // .font(.footnote) + // .foregroundColor(.secondary) + // .multilineTextAlignment(.center) + // .padding(.horizontal) + // NavigationLink(destination: { LoginView() .navigationBarTitleDisplayMode(.inline) @@ -54,9 +58,9 @@ struct OnboardingView: View { .cornerRadius(10) }).padding() } + .navigationBarHidden(true) + .navigationBarTitleDisplayMode(.inline) } - .navigationBarHidden(true) - .navigationBarTitleDisplayMode(.inline) } } diff --git a/BA-Schedule/Sources/Views/Schedule/ScheduleView.swift b/BA-Schedule/Sources/Views/Schedule/ScheduleView.swift index 1322837..9b6eb83 100644 --- a/BA-Schedule/Sources/Views/Schedule/ScheduleView.swift +++ b/BA-Schedule/Sources/Views/Schedule/ScheduleView.swift @@ -17,7 +17,12 @@ struct ScheduleView: View { @State private var isInitialLoading: Bool = true @State private var noNetwork: Bool = false + @State private var showCalendarSheet = false + @State private var showAboutThisApp = false + @State private var showLoginSheet = false + @EnvironmentObject var settings: Settings + @Environment(\.presentationMode) var presentationMode private var logger: Logger = Logger.init(for: "ScheduleView") @@ -28,15 +33,55 @@ struct ScheduleView: View { var body: some View { NavigationView { ScheduleListView(studyDays: self.$studyDays, lastOnlineUpdate: self.settings.lastOnlineUpdate) - .navigationTitle("SCHEDULE") - .onAppear { - Task { - await self.loadSchedule() + .navigationTitle("SCHEDULE") + .onAppear { + Task { + await self.loadSchedule() + } + } + .refreshable { + await self.loadSchedule(forceUpdate: true) + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Menu { + Button { + showCalendarSheet = true + } label: { + Label { + Text("CALENDAR_EXPORT_MENU") + } icon: { + Image(systemName: "calendar.badge.plus") + } + } + Divider() + Button { + UIApplication.shared.open(URL(string: "mailto:kontakt@jonasrichter.eu?subject=Vorschlag%20zu%20BA-Schedule")!) + } label: { + Label("SETTINGS.IDEAS.BUTTON", systemImage: "envelope") + } + Divider() + Button { + showLoginSheet = true + } label: { + Label("SETTINGS.USER.CHANGELOGIN", systemImage: "person.fill.and.arrow.left.and.arrow.right") + } + Button { + showAboutThisApp = true + } label: { + Label { + Text("SETTINGS.ABOUT") + } icon: { + Image(systemName: "questionmark.app") + } + } + + } label: { + Label("SETTINGS.MOREFUNCTIONS.HEADER", systemImage: "ellipsis.circle") + } + + } } - } - .refreshable { - await self.loadSchedule(forceUpdate: true) - } } .overlay(alignment: .center) { if isInitialLoading || !self.settings.isOnboarded { @@ -54,6 +99,22 @@ struct ScheduleView: View { } } } + .sheet(isPresented: $showCalendarSheet) { + if #available(iOS 16.0, *) { + ExportToCalendarView() + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + } else { + ExportToCalendarView() + } + } + .sheet(isPresented: $showAboutThisApp) { + AboutView() + } + .sheet(isPresented: $showLoginSheet) { + LoginView() + } + .navigationViewStyle(.stack) } @@ -122,5 +183,6 @@ struct ScheduleView_Previews: PreviewProvider { static var previews: some View { ScheduleView() .environmentObject(Settings()) + .environment(\.locale, .init(identifier: "de")) } } diff --git a/BA-Schedule/Sources/Views/Settings/AboutView.swift b/BA-Schedule/Sources/Views/Settings/AboutView.swift index 519c15e..3d75480 100644 --- a/BA-Schedule/Sources/Views/Settings/AboutView.swift +++ b/BA-Schedule/Sources/Views/Settings/AboutView.swift @@ -8,54 +8,82 @@ import SwiftUI struct AboutView: View { - + @Environment(\.presentationMode) var presentationMode var version: String { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String } - var buildNumber: String { Bundle.main.infoDictionary?["CFBundleVersion"] as! String } + var body: some View { - List { - Section(content: { - VStack(alignment: .leading) { - Text("Icon") - .font(.footnote) - .fontWeight(.bold) - Text("Tri Hartono (The Noun Project)") + NavigationView { + List { + Section(content: { + VStack(alignment: .leading) { + Text("Icon") + .font(.footnote) + .fontWeight(.bold) + Text("Tri Hartono (The Noun Project)") + } + VStack(alignment: .leading) { + Text("Made by") + .font(.footnote) + .fontWeight(.bold) + Text("Jonas Richard Richter") + } + }, header: { + VStack(alignment: .center) { + Image("Icon") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 120) + .cornerRadius(30) + .shadow(radius: 10) + Text("BA-Schedule") + .font(.largeTitle) + .fontWeight(.bold) + .textCase(.none) + Text("\(version) (\(buildNumber))") + .font(.footnote) + .foregroundColor(.gray) + .padding(.bottom) + } + .frame(maxWidth: .infinity, alignment: .center) + }) + + Section { + Button { + UIApplication.shared.open(URL.BaSchedule.github) + } label: { + Label("SETTINGS.GITHUB-LINK", systemImage: "text.and.command.macwindow") + } } - VStack(alignment: .leading) { - Text("Made by") - .font(.footnote) - .fontWeight(.bold) - Text("Jonas Richard Richter") + + Section { + Text("Florian Schmidt") + .badge("@greybaron") + } header: { + Text("ABOUT.THANKSTO") } - }, header: { - VStack(alignment: .center) { - Image("Icon") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 120) - .cornerRadius(30) - .padding(.top) - .shadow(radius: 10) - Text("BA-Schedule") - .font(.largeTitle) - .fontWeight(.bold) - .textCase(.none) - Text("\(version) (\(buildNumber))") - .font(.footnote) - .foregroundColor(.gray) - .padding(.bottom) + } + .listStyle(.insetGrouped) + .navigationTitle("SETTINGS.ABOUT") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + presentationMode.wrappedValue.dismiss() + } label: { + Label("GENERAL_CLOSE", systemImage: "xmark") + } + .buttonStyle(.bordered) + .labelStyle(.titleOnly) + .buttonBorderShape(.capsule) } - .frame(maxWidth: .infinity, alignment: .center) - }) + } } - .listStyle(.insetGrouped) - .navigationTitle("SETTINGS.ABOUT") - .navigationBarTitleDisplayMode(.inline) } } diff --git a/BA-Schedule/Sources/Views/Settings/SettingsView.swift b/BA-Schedule/Sources/Views/Settings/SettingsView.swift deleted file mode 100644 index 0ea408a..0000000 --- a/BA-Schedule/Sources/Views/Settings/SettingsView.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// SettingsView.swift -// BA-Schedule -// -// Created by Jonas Richard Richter on 30.01.22. -// - -import SwiftUI - -struct SettingsView: View { - - // MARK: - Properties - - @EnvironmentObject var settings: Settings - - @State private var showLogin = false - - // MARK: - View - - var body: some View { - NavigationView { - List { - - // MARK: - User - Section { - if self.settings.isOnboarded { - HStack { - Image(systemName: "person.crop.circle.badge.checkmark") - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 40, height: 40) - .padding(.vertical) - .symbolRenderingMode(.multicolor) - VStack(alignment: .leading) { - Text("SETTINGS.LOGGEDIN.TITLE") - .font(.callout) - .bold() - .multilineTextAlignment(.center) - Text("Matrikel: \(settings.username ?? "unknown")") - .font(.footnote) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - } - .padding() - } - Button { - self.showLogin = true - } label: { - Label("SETTINGS.USER.CHANGELOGIN", systemImage: "person.fill.and.arrow.left.and.arrow.right") - } - } else { - Button { - self.showLogin = true - } label: { - HStack { - Image(systemName: "person.crop.circle.badge.xmark") - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 40, height: 40) - .padding(.vertical) - .symbolRenderingMode(.multicolor) - VStack(alignment: .center) { - Text("SETTINGS.NOTLOGGEDIN.TITLE") - .font(.callout) - .bold() - .multilineTextAlignment(.center) - Text("SETTINGS.NOTLOGGEDIN.DESCR") - .font(.footnote) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - } - .frame(maxWidth: .infinity, alignment: .center) - .padding() - } - } - .buttonStyle(.plain) - - } - } - .sheet(isPresented: self.$showLogin) { - LoginView() - } - - // MARK: - More Functions - - Section { - Toggle(isOn: self.$settings.useOfflineSupport) { - Label("SETTINGS.MOREFUNCTIONS.OFFLINESUPPORT", systemImage: "internaldrive") - } - Toggle(isOn: self.$settings.showInstructor) { - Label("SETTINGS.MOREFUNCTIONS.SHOWINSTRUCTUR", systemImage: "person.circle") - } - Toggle(isOn: self.$settings.showRemarks) { - Label("SETTINGS.MOREFUNCTIONS.SHOWREMARKS", systemImage: "info.circle") - } - - VStack(alignment: .center) { - Text("SETTINGS.IDEAS") - .font(.headline) - Button { - UIApplication.shared.open(URL(string: "mailto:kontakt@jonasrichter.eu?subject=Vorschlag%20zu%20BA-Schedule")!) - } label: { - Label("SETTINGS.IDEAS.BUTTON", systemImage: "envelope") - } - .buttonStyle(.bordered) - - } - .frame(maxWidth: .infinity, alignment: .center) - .padding(.vertical) - } header: { - Text("SETTINGS.MOREFUNCTIONS.HEADER") - } - - - // MARK: - About - Section { - Button { - UIApplication.shared.open(URL.BaSchedule.github) - } label: { - Label("SETTINGS.GITHUB-LINK", systemImage: "text.and.command.macwindow") - } - - NavigationLink(destination: { - AboutView() - }, label: { - Label("SETTINGS.ABOUT", systemImage: "questionmark.app") - .foregroundColor(.primary) - }) - } - } - .listStyle(.insetGrouped) - .navigationTitle("SETTINGS") - } - } -} - -struct SettingsView_Previews: PreviewProvider { - static var previews: some View { - SettingsView() - .environment(\.locale, .init(identifier: "de")) - .environmentObject(Settings()) - } -} diff --git a/BA-Schedule/de.lproj/InfoPlist.strings b/BA-Schedule/de.lproj/InfoPlist.strings new file mode 100644 index 0000000..80fb34e --- /dev/null +++ b/BA-Schedule/de.lproj/InfoPlist.strings @@ -0,0 +1,8 @@ +/* + InfoPlist.strings + BA-Schedule + + Created by Jonas Richard Richter on 26.09.22. + +*/ +"NSCalendarsUsageDescription" = "BA-Schedule benötigt Kalenderzugriff um deinen Stundenplan zu exportieren."; diff --git a/BA-Schedule/en.lproj/InfoPlist.strings b/BA-Schedule/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..8afe686 --- /dev/null +++ b/BA-Schedule/en.lproj/InfoPlist.strings @@ -0,0 +1,8 @@ +/* + InfoPlist.strings + BA-Schedule + + Created by Jonas Richard Richter on 26.09.22. + +*/ +"NSCalendarsUsageDescription" = "BA-Schedule requires calendar access to export your timetable.";