diff --git a/DHBW-Service.xcodeproj/project.pbxproj b/DHBW-Service.xcodeproj/project.pbxproj index 2d2e9e2..c39a93f 100644 --- a/DHBW-Service.xcodeproj/project.pbxproj +++ b/DHBW-Service.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ CD730A35259A860E00E0BB69 /* SettingsAcknowledgements.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */; }; CD8555BE25C47AE500C4ACD6 /* RaPlaFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8555BD25C47AE500C4ACD6 /* RaPlaFetcher.swift */; }; CD8555C325C47B5300C4ACD6 /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8555C225C47B5300C4ACD6 /* ApiService.swift */; }; + CD9E3F0F25DC466100C77D10 /* SettingsPushNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9E3F0E25DC466100C77D10 /* SettingsPushNotifications.swift */; }; CD9FAB81258EC60200D6D0C5 /* DHBW_ServiceApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB80258EC60200D6D0C5 /* DHBW_ServiceApp.swift */; }; CD9FAB83258EC60200D6D0C5 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB82258EC60200D6D0C5 /* ContentView.swift */; }; CD9FAB85258EC60600D6D0C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD9FAB84258EC60600D6D0C5 /* Assets.xcassets */; }; @@ -69,6 +70,8 @@ CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAcknowledgements.swift; sourceTree = ""; }; CD8555BD25C47AE500C4ACD6 /* RaPlaFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RaPlaFetcher.swift; sourceTree = ""; }; CD8555C225C47B5300C4ACD6 /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = ""; }; + CD9E3F0A25DC3C9A00C77D10 /* DHBW-Service.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "DHBW-Service.entitlements"; sourceTree = ""; }; + CD9E3F0E25DC466100C77D10 /* SettingsPushNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPushNotifications.swift; sourceTree = ""; }; CD9FAB7D258EC60200D6D0C5 /* DHBW-Service.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DHBW-Service.app"; sourceTree = BUILT_PRODUCTS_DIR; }; CD9FAB80258EC60200D6D0C5 /* DHBW_ServiceApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DHBW_ServiceApp.swift; sourceTree = ""; }; CD9FAB82258EC60200D6D0C5 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -179,6 +182,7 @@ isa = PBXGroup; children = ( CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */, + CD9E3F0E25DC466100C77D10 /* SettingsPushNotifications.swift */, ); path = SettingsSubViews; sourceTree = ""; @@ -206,6 +210,7 @@ CD9FAB7F258EC60200D6D0C5 /* DHBW-Service */ = { isa = PBXGroup; children = ( + CD9E3F0A25DC3C9A00C77D10 /* DHBW-Service.entitlements */, CDCD720F25912D3C00FBF2F5 /* App */, CDCD721025912D4900FBF2F5 /* Views */, CDDCF4792591FE410027CDC5 /* Utility */, @@ -475,6 +480,7 @@ CD9FAB8A258EC60600D6D0C5 /* Persistence.swift in Sources */, CD730A35259A860E00E0BB69 /* SettingsAcknowledgements.swift in Sources */, CD9FAB83258EC60200D6D0C5 /* ContentView.swift in Sources */, + CD9E3F0F25DC466100C77D10 /* SettingsPushNotifications.swift in Sources */, CDCD72242591316500FBF2F5 /* LocalSettings.swift in Sources */, CDD970E125D453D90061755E /* RaPlaEvent+CoreDataClass.swift in Sources */, CDD970DD25D453D90061755E /* User+CoreDataClass.swift in Sources */, @@ -675,8 +681,11 @@ CD9FABA8258EC60600D6D0C5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "DHBW-Service/DHBW-Service.entitlements"; CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0.1; DEVELOPMENT_ASSET_PATHS = "\"DHBW-Service/Preview Content\""; DEVELOPMENT_TEAM = HS7KNT4MZ2; ENABLE_PREVIEWS = YES; @@ -696,8 +705,11 @@ CD9FABA9258EC60600D6D0C5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "DHBW-Service/DHBW-Service.entitlements"; CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0.1; DEVELOPMENT_ASSET_PATHS = "\"DHBW-Service/Preview Content\""; DEVELOPMENT_TEAM = HS7KNT4MZ2; ENABLE_PREVIEWS = YES; diff --git a/DHBW-Service/App/DHBW_ServiceApp.swift b/DHBW-Service/App/DHBW_ServiceApp.swift index 7ca1eab..4be7fa7 100644 --- a/DHBW-Service/App/DHBW_ServiceApp.swift +++ b/DHBW-Service/App/DHBW_ServiceApp.swift @@ -6,12 +6,14 @@ // import SwiftUI +import UserNotifications @main struct DHBW_ServiceApp: App { + @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate let persistenceController = PersistenceController.shared let settings = LocalSettings() - + var body: some Scene { WindowGroup { ContentView() @@ -20,3 +22,21 @@ struct DHBW_ServiceApp: App { } } } + +//*** Implement App delegate ***// +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + return true + } + + //No callback in simulator + //-- must use device to get valid push token + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } + let token = tokenParts.joined() + print("Device Token: \(token)") + } + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + print(error.localizedDescription) + } +} diff --git a/DHBW-Service/CoreData/DHBW_Service.xcdatamodeld/DHBW_Service.xcdatamodel/contents b/DHBW-Service/CoreData/DHBW_Service.xcdatamodeld/DHBW_Service.xcdatamodel/contents index db914b6..69c4074 100644 --- a/DHBW-Service/CoreData/DHBW_Service.xcdatamodeld/DHBW_Service.xcdatamodel/contents +++ b/DHBW-Service/CoreData/DHBW_Service.xcdatamodeld/DHBW_Service.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -20,10 +20,11 @@ + - + \ No newline at end of file diff --git a/DHBW-Service/CoreData/Persistence.swift b/DHBW-Service/CoreData/Persistence.swift index 088e5cc..1980513 100644 --- a/DHBW-Service/CoreData/Persistence.swift +++ b/DHBW-Service/CoreData/Persistence.swift @@ -10,7 +10,7 @@ import CoreData struct PersistenceController { // Singleton static let shared = PersistenceController() - + // Cloud Kit container let container: NSPersistentCloudKitContainer @@ -20,7 +20,7 @@ struct PersistenceController { return self.container.viewContext } } - + // MARK: - Constructor init(inMemory: Bool = false) { container = NSPersistentCloudKitContainer(name: "DHBW_Service") @@ -31,15 +31,15 @@ struct PersistenceController { if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - + /* - Typical reasons for an error here include: - * The parent directory does not exist, cannot be created, or disallows writing. - * The persistent store is not accessible, due to permissions or data protection when the device is locked. - * The device is out of space. - * The store could not be migrated to the current model version. - Check the error message to determine what the actual problem was. - */ + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ fatalError("Unresolved error \(error), \(error.userInfo)") } }) @@ -90,6 +90,18 @@ struct PersistenceController { normalEvent2.category = "Lehrveranstaltung" examEvent.category = "Prüfung" + + let lecturer1 = Lecturer(context: PersistenceController.shared.context) + let lecturer2 = Lecturer(context: PersistenceController.shared.context) + lecturer1.name = "Mustermann, Prof. Dr." + lecturer1.email = "mustermann@dhbw-karlsruhe.de" + lecturer2.name = "Musterfrau, Prof. Dr." + lecturer2.email = "musterfrau@dhbw-karlsruhe.de" + normalEvent1.addToLecturers(lecturer1) + normalEvent2.addToLecturers(lecturer2) + examEvent.addToLecturers(lecturer1) + examEvent.addToLecturers(lecturer2) + var currentDate = Date() currentDate.addTimeInterval(1*60*60);normalEvent1.startDate = currentDate currentDate.addTimeInterval(1*60*60);normalEvent2.startDate = currentDate diff --git a/DHBW-Service/DHBW-Service.entitlements b/DHBW-Service/DHBW-Service.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/DHBW-Service/DHBW-Service.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/DHBW-Service/Supporting Files/Info.plist b/DHBW-Service/Supporting Files/Info.plist index 3829413..1cbbe6f 100644 --- a/DHBW-Service/Supporting Files/Info.plist +++ b/DHBW-Service/Supporting Files/Info.plist @@ -20,14 +20,20 @@ - CFBundlePrimaryIcon + + CFBundleIcons~ipad + + CFBundleAlternateIcons - CFBundleIconFiles - - dhbw-standard-icon - - UIPrerenderedIcon - + Alpaca-Alt-Icon + + CFBundleIconFiles + + alpaca-alt-icon + + UIPrerenderedIcon + + CFBundleIdentifier @@ -41,7 +47,9 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + LSRequiresIPhoneOS UIApplicationSceneManifest diff --git a/DHBW-Service/Utility/RaPlaFetcher.swift b/DHBW-Service/Utility/RaPlaFetcher.swift index 90d2ef2..e907d92 100644 --- a/DHBW-Service/Utility/RaPlaFetcher.swift +++ b/DHBW-Service/Utility/RaPlaFetcher.swift @@ -10,14 +10,21 @@ import CoreData class RaPlaFetcher { public class iCalEvent { - var startDate: Date = Date() //DTSTART - var endDate: Date = Date() //DTEND - var summary: String = "" //SUMMARY - var description: String = "" //DESCRIPTION - var location: String = "" //LOCATION - var category: String = "" //CATEGORIES - var uid: String = "" //UID - var lecturers: [LecturerObj] = [] //ATTENDEE + var startDate: Date = Date() // DTSTART + var endDate: Date = Date() // DTEND + var summary: String = "" // SUMMARY + var description: String = "" // DESCRIPTION + var location: String = "" // LOCATION + var category: String = "" // CATEGORIES + var uid: String = "" // UID + var lecturers: [LecturerObj] = [] // ATTENDEE + var excludedDates: [Date] = [] // EXDATE + var isRecurring: Bool = false // If the event is recurring + var frequency: String = "" // Frequence in case of recurring events, e.g. DAILY or WEEKLY + var recCount: Int = 0 // How often the event occurs in case of recurring events + var recInterval: Int = 0 // Interval of the recurring event, e.g. 1 for every week / day / ... + var recDay: String = "" // The day of the recurrence, e.g. FR for friday + var recUntil: Date = Date() // Until when the event has to be repeated } public class LecturerObj { @@ -84,7 +91,14 @@ class RaPlaFetcher { for lineNr in 0...lines.count-1 { if(lines[lineNr].hasPrefix(" ")){ lines[lineNr] = String(lines[lineNr].dropFirst()) - lines[lineNr-1].append(lines[lineNr]) + + // If there are more than 2 lines that have to be merged, we need to find out how many lines we have to go up + var goUp = 1 + while(lines[lineNr-goUp] == ""){ + goUp += 1 + } + + lines[lineNr-goUp].append(lines[lineNr]) lines[lineNr] = "" } } @@ -144,6 +158,57 @@ class RaPlaFetcher { lecturer.email = lecturerEmail evt.lecturers.append(lecturer) + } else if(line.hasPrefix("RRULE")) { + // This line normally looks like this: RRULE:FREQ=WEEKLY;COUNT=12;INTERVAL=1;BYDAY=TU + // Can also look smth like this though: RRULE:FREQ=WEEKLY;COUNT=12 + + evt.isRecurring = true + + let params = lineWithoutPrefix.components(separatedBy: ";") + + for param in params { + let keyword = param[param.startIndex.. Bool{ + // Get known UIDs let existingEvents: [RaPlaEvent] = RaPlaEvent.getAll() var existingEventsDict: [String:RaPlaEvent] = [:] for event in existingEvents { existingEventsDict[event.uid!] = event } - let newEventUIDs = eventObjects.map{$0.uid} + // List for new UIDs + var newEventUIDs: [String] = [] for event in eventObjects { // If the event already exists locally, update it. Otherwise, create a new record - let evt: RaPlaEvent - if existingEventsDict.keys.contains(event.uid) { - evt = existingEventsDict[event.uid]! + if(event.isRecurring){ + // Create as many events as we need + // If we e.g. need 12 events, we create 0...11 + for iteration in 0..