mirror of
				https://github.com/Mueller-Patrick/DHBW-Service-App.git
				synced 2025-11-04 02:35:47 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			d7f078cc88
			...
			06b66a50a5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 06b66a50a5 | |||
| 02cd3a0db9 | 
| 
						 | 
				
			
			@ -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 = "<group>"; };
 | 
			
		||||
		CD8555BD25C47AE500C4ACD6 /* RaPlaFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RaPlaFetcher.swift; sourceTree = "<group>"; };
 | 
			
		||||
		CD8555C225C47B5300C4ACD6 /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = "<group>"; };
 | 
			
		||||
		CD9E3F0A25DC3C9A00C77D10 /* DHBW-Service.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "DHBW-Service.entitlements"; sourceTree = "<group>"; };
 | 
			
		||||
		CD9E3F0E25DC466100C77D10 /* SettingsPushNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPushNotifications.swift; sourceTree = "<group>"; };
 | 
			
		||||
		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 = "<group>"; };
 | 
			
		||||
		CD9FAB82258EC60200D6D0C5 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
 | 
			
		||||
| 
						 | 
				
			
			@ -179,6 +182,7 @@
 | 
			
		|||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */,
 | 
			
		||||
				CD9E3F0E25DC466100C77D10 /* SettingsPushNotifications.swift */,
 | 
			
		||||
			);
 | 
			
		||||
			path = SettingsSubViews;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,9 +6,11 @@
 | 
			
		|||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
import UserNotifications
 | 
			
		||||
 | 
			
		||||
@main
 | 
			
		||||
struct DHBW_ServiceApp: App {
 | 
			
		||||
    @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
 | 
			
		||||
    let persistenceController = PersistenceController.shared
 | 
			
		||||
    let settings = LocalSettings()
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 | 
			
		||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D62" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
 | 
			
		||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20E5172i" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
 | 
			
		||||
    <entity name="Lecturer" representedClassName="Lecturer" syncable="YES">
 | 
			
		||||
        <attribute name="email" optional="YES" attributeType="String"/>
 | 
			
		||||
        <attribute name="name" optional="YES" attributeType="String"/>
 | 
			
		||||
| 
						 | 
				
			
			@ -20,10 +20,11 @@
 | 
			
		|||
        <attribute name="course" optional="YES" attributeType="String"/>
 | 
			
		||||
        <attribute name="director" optional="YES" attributeType="String"/>
 | 
			
		||||
        <attribute name="name" optional="YES" attributeType="String"/>
 | 
			
		||||
        <attribute name="raplaLink" optional="YES" attributeType="String"/>
 | 
			
		||||
    </entity>
 | 
			
		||||
    <elements>
 | 
			
		||||
        <element name="Lecturer" positionX="-351" positionY="99" width="128" height="74"/>
 | 
			
		||||
        <element name="RaPlaEvent" positionX="-271.3642578125" positionY="83.64776611328125" width="128" height="164"/>
 | 
			
		||||
        <element name="User" positionX="-428.6358032226562" positionY="2.067169189453125" width="128" height="74"/>
 | 
			
		||||
        <element name="User" positionX="-428.6358032226562" positionY="2.067169189453125" width="128" height="89"/>
 | 
			
		||||
    </elements>
 | 
			
		||||
</model>
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										8
									
								
								DHBW-Service/DHBW-Service.entitlements
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								DHBW-Service/DHBW-Service.entitlements
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 | 
			
		||||
<plist version="1.0">
 | 
			
		||||
<dict>
 | 
			
		||||
	<key>aps-environment</key>
 | 
			
		||||
	<string>development</string>
 | 
			
		||||
</dict>
 | 
			
		||||
</plist>
 | 
			
		||||
| 
						 | 
				
			
			@ -20,16 +20,22 @@
 | 
			
		|||
				<false/>
 | 
			
		||||
			</dict>
 | 
			
		||||
		</dict>
 | 
			
		||||
		<key>CFBundlePrimaryIcon</key>
 | 
			
		||||
	</dict>
 | 
			
		||||
	<key>CFBundleIcons~ipad</key>
 | 
			
		||||
	<dict>
 | 
			
		||||
		<key>CFBundleAlternateIcons</key>
 | 
			
		||||
		<dict>
 | 
			
		||||
			<key>Alpaca-Alt-Icon</key>
 | 
			
		||||
			<dict>
 | 
			
		||||
				<key>CFBundleIconFiles</key>
 | 
			
		||||
				<array>
 | 
			
		||||
				<string>dhbw-standard-icon</string>
 | 
			
		||||
					<string>alpaca-alt-icon</string>
 | 
			
		||||
				</array>
 | 
			
		||||
				<key>UIPrerenderedIcon</key>
 | 
			
		||||
				<false/>
 | 
			
		||||
			</dict>
 | 
			
		||||
		</dict>
 | 
			
		||||
	</dict>
 | 
			
		||||
	<key>CFBundleIdentifier</key>
 | 
			
		||||
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 | 
			
		||||
	<key>CFBundleInfoDictionaryVersion</key>
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +47,9 @@
 | 
			
		|||
	<key>CFBundleShortVersionString</key>
 | 
			
		||||
	<string>1.0</string>
 | 
			
		||||
	<key>CFBundleVersion</key>
 | 
			
		||||
	<string>1</string>
 | 
			
		||||
	<string>$(CURRENT_PROJECT_VERSION)</string>
 | 
			
		||||
	<key>ITSAppUsesNonExemptEncryption</key>
 | 
			
		||||
	<false/>
 | 
			
		||||
	<key>LSRequiresIPhoneOS</key>
 | 
			
		||||
	<true/>
 | 
			
		||||
	<key>UIApplicationSceneManifest</key>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,13 @@ class RaPlaFetcher {
 | 
			
		|||
        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..<param.firstIndex(of: "=")!]
 | 
			
		||||
                        let value = param[param.index(param.firstIndex(of: "=")!, offsetBy: 1)..<param.endIndex]
 | 
			
		||||
                        
 | 
			
		||||
                        switch keyword {
 | 
			
		||||
                        case "FREQ":
 | 
			
		||||
                            evt.frequency = String(value)
 | 
			
		||||
                            break
 | 
			
		||||
                        case "COUNT":
 | 
			
		||||
                            evt.recCount = Int(value)!
 | 
			
		||||
                            break
 | 
			
		||||
                        case "INTERVAL":
 | 
			
		||||
                            evt.recInterval = Int(value)!
 | 
			
		||||
                            break
 | 
			
		||||
                        case "BYDAY":
 | 
			
		||||
                            evt.recDay = String(value)
 | 
			
		||||
                            break
 | 
			
		||||
                        case "UNTIL":
 | 
			
		||||
                            let dateFormatter = DateFormatter()
 | 
			
		||||
                            dateFormatter.dateFormat = "yyyyMMdd"
 | 
			
		||||
                            let date = dateFormatter.date(from: String(value))!
 | 
			
		||||
                            evt.recUntil = date
 | 
			
		||||
                            break
 | 
			
		||||
                        default:
 | 
			
		||||
                            print("Unknown parameter found: " + line)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (line.hasPrefix("EXDATE")) {
 | 
			
		||||
                    // Excluded from recurring events
 | 
			
		||||
                    // Format: 20210401T133000,20210408T133000,20210513T133000
 | 
			
		||||
                    
 | 
			
		||||
                    let exclDateStrings = lineWithoutPrefix.components(separatedBy: ",")
 | 
			
		||||
                    
 | 
			
		||||
                    for exclDate in exclDateStrings {
 | 
			
		||||
                        let dateFormatter = DateFormatter()
 | 
			
		||||
                        if(lineWithoutPrefix.contains("Z")){
 | 
			
		||||
                            dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
 | 
			
		||||
                        } else {
 | 
			
		||||
                            dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss"
 | 
			
		||||
                        }
 | 
			
		||||
                        let date = dateFormatter.date(from: exclDate)!
 | 
			
		||||
                        evt.excludedDates.append(date)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
| 
						 | 
				
			
			@ -156,37 +221,125 @@ class RaPlaFetcher {
 | 
			
		|||
    // Save the given iCalEvent objects to CoreData
 | 
			
		||||
    // Updates the events if they already exist and deletes old (/invalid) ones
 | 
			
		||||
    private class func saveToCoreData(eventObjects: [iCalEvent]) -> 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
 | 
			
		||||
            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..<event.recCount {
 | 
			
		||||
                    // Calculate start- and enddate
 | 
			
		||||
                    // Calculate offset
 | 
			
		||||
                    let offsetType: Calendar.Component
 | 
			
		||||
                    let offsetAmount: Int
 | 
			
		||||
                    
 | 
			
		||||
                    switch event.frequency {
 | 
			
		||||
                    case "DAILY":
 | 
			
		||||
                        offsetType = Calendar.Component.day
 | 
			
		||||
                        offsetAmount = 1
 | 
			
		||||
                        break
 | 
			
		||||
                    case "WEEKLY":
 | 
			
		||||
                        offsetType = Calendar.Component.day
 | 
			
		||||
                        offsetAmount = 7
 | 
			
		||||
                        break
 | 
			
		||||
                    case "MONTHLY":
 | 
			
		||||
                        offsetType = Calendar.Component.month
 | 
			
		||||
                        offsetAmount = 1
 | 
			
		||||
                        break
 | 
			
		||||
                    case "YEARLY":
 | 
			
		||||
                        offsetType = Calendar.Component.year
 | 
			
		||||
                        offsetAmount = 1
 | 
			
		||||
                        break
 | 
			
		||||
                    default:
 | 
			
		||||
                        offsetType = Calendar.Component.day
 | 
			
		||||
                        offsetAmount = 0
 | 
			
		||||
                        print("Found unknown frequency: " + event.frequency)
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    let startDate = Calendar.current.date(byAdding: offsetType, value: (offsetAmount * iteration), to: event.startDate)!
 | 
			
		||||
                    let endDate = Calendar.current.date(byAdding: offsetType, value: (offsetAmount * iteration), to: event.endDate)!
 | 
			
		||||
                    
 | 
			
		||||
                    // Check if this recurrence should be excluded
 | 
			
		||||
                    if(event.excludedDates.contains(startDate)){
 | 
			
		||||
                        continue
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Generate UID
 | 
			
		||||
                    // Appending iteration to distinguish between recurring events
 | 
			
		||||
                    let newUID = event.uid + "---" + String(iteration)
 | 
			
		||||
                    
 | 
			
		||||
                    // Create or update existing CoreData object
 | 
			
		||||
                    let evt: RaPlaEvent
 | 
			
		||||
            if existingEventsDict.keys.contains(event.uid) {
 | 
			
		||||
                evt = existingEventsDict[event.uid]!
 | 
			
		||||
                    if existingEventsDict.keys.contains(newUID) {
 | 
			
		||||
                        evt = existingEventsDict[newUID]!
 | 
			
		||||
                    } else {
 | 
			
		||||
                        evt = RaPlaEvent(context: PersistenceController.shared.context)
 | 
			
		||||
                        
 | 
			
		||||
                        // Set default values for new object
 | 
			
		||||
                        evt.isHidden = false
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Populate fields
 | 
			
		||||
                    // (offsetAmount * iteration) because for the 1st event, we dont want to add an offset, and
 | 
			
		||||
                    // for every event after that we want to add e.g. 1 week, 2 weeks, 3 weeks etc.
 | 
			
		||||
                    evt.startDate = startDate
 | 
			
		||||
                    evt.endDate = endDate
 | 
			
		||||
                    evt.summary = event.summary
 | 
			
		||||
                    evt.descr = event.description
 | 
			
		||||
                    evt.location = event.location
 | 
			
		||||
                    evt.category = event.category
 | 
			
		||||
                    evt.uid = newUID
 | 
			
		||||
                    for lecturer in event.lecturers {
 | 
			
		||||
                        let lect = Lecturer(context: PersistenceController.shared.context)
 | 
			
		||||
                        lect.name = lecturer.name
 | 
			
		||||
                        lect.email = lecturer.email
 | 
			
		||||
                        lect.event = evt
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Add UID to new UIDs list
 | 
			
		||||
                    newEventUIDs.append(newUID)
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Generate UID
 | 
			
		||||
                let newUID = event.uid + "---0"  // Appending ---0 to distinguish between recurring events
 | 
			
		||||
                
 | 
			
		||||
                // Create or update existing CoreData object
 | 
			
		||||
                let evt: RaPlaEvent
 | 
			
		||||
                if existingEventsDict.keys.contains(newUID) {
 | 
			
		||||
                    evt = existingEventsDict[newUID]!
 | 
			
		||||
                } else {
 | 
			
		||||
                    evt = RaPlaEvent(context: PersistenceController.shared.context)
 | 
			
		||||
                    
 | 
			
		||||
                    // Set default values for new object
 | 
			
		||||
                    evt.isHidden = false
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Populate fields
 | 
			
		||||
                evt.startDate = event.startDate
 | 
			
		||||
                evt.endDate = event.endDate
 | 
			
		||||
                evt.summary = event.summary
 | 
			
		||||
                evt.descr = event.description
 | 
			
		||||
                evt.location = event.location
 | 
			
		||||
                evt.category = event.category
 | 
			
		||||
            evt.uid = event.uid
 | 
			
		||||
                evt.uid = newUID
 | 
			
		||||
                for lecturer in event.lecturers {
 | 
			
		||||
                    let lect = Lecturer(context: PersistenceController.shared.context)
 | 
			
		||||
                    lect.name = lecturer.name
 | 
			
		||||
                    lect.email = lecturer.email
 | 
			
		||||
                    lect.event = evt
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Add UID to new UIDs list
 | 
			
		||||
                newEventUIDs.append(newUID)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Now we also have to delete locally stored events that have been deleted from RaPla
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,10 @@ import CoreData
 | 
			
		|||
struct FirstOpeningSettings: View {
 | 
			
		||||
    @EnvironmentObject var settings: LocalSettings
 | 
			
		||||
    @State private var name = ""
 | 
			
		||||
    @State private var dhbwLocation = ""
 | 
			
		||||
    @State private var course = ""
 | 
			
		||||
    @State private var director = ""
 | 
			
		||||
    @State private var raplaLink = ""
 | 
			
		||||
    @State private var invalidInputName = false
 | 
			
		||||
    @State private var invalidInputCourse = false
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -28,10 +30,18 @@ struct FirstOpeningSettings: View {
 | 
			
		|||
                .frame(minWidth: 200, idealWidth: nil, maxWidth: 500, minHeight: nil, idealHeight: nil, maxHeight: nil, alignment: .center)
 | 
			
		||||
                .padding(.horizontal)
 | 
			
		||||
            
 | 
			
		||||
            Picker(selection: self.$dhbwLocation, label: Text("Location")){
 | 
			
		||||
                Text("Karlsruhe")
 | 
			
		||||
                Text("Mannheim")
 | 
			
		||||
                Text("Stuttgart")
 | 
			
		||||
                Text("Mosbach")
 | 
			
		||||
            }.frame(maxWidth: 500, alignment: .center)
 | 
			
		||||
            .pickerStyle(SegmentedPickerStyle())
 | 
			
		||||
            
 | 
			
		||||
            TextField("course".localized(tableName: "General"), text: self.$course)
 | 
			
		||||
                .overlay(RoundedRectangle(cornerRadius: 10).stroke(invalidInputCourse ? Color.red : Color.secondary, lineWidth: 1))
 | 
			
		||||
                .onChange(of: course, perform: { value in
 | 
			
		||||
                    self.setDirector()
 | 
			
		||||
                    self.setCourseInfo()
 | 
			
		||||
                    self.course = self.course.uppercased()
 | 
			
		||||
                })
 | 
			
		||||
                .foregroundColor(invalidInputCourse ? .red : .primary)
 | 
			
		||||
| 
						 | 
				
			
			@ -65,10 +75,12 @@ struct FirstOpeningSettings: View {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
extension FirstOpeningSettings{
 | 
			
		||||
    func setDirector() {
 | 
			
		||||
        if (course == "TINF19B4") {
 | 
			
		||||
    func setCourseInfo() {
 | 
			
		||||
        // TODO: Replace this with some database query or stuff like this to load actual data
 | 
			
		||||
        switch course {
 | 
			
		||||
            case "TINF19B4":
 | 
			
		||||
                director = "Jörn Eisenbiegler"
 | 
			
		||||
        } else {
 | 
			
		||||
            default:
 | 
			
		||||
                director = ""
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,11 @@ struct SettingsMain: View {
 | 
			
		|||
                        label: {
 | 
			
		||||
                            Text("Acknowledgements")
 | 
			
		||||
                        })
 | 
			
		||||
                    NavigationLink(
 | 
			
		||||
                        destination: SettingsPushNotifications(),
 | 
			
		||||
                        label: {
 | 
			
		||||
                            Text("Push Notifications")
 | 
			
		||||
                        })
 | 
			
		||||
                    Button(action: {
 | 
			
		||||
                        self.showLogoutConfirmationAlert = true
 | 
			
		||||
                    }, label: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
//
 | 
			
		||||
//  SettingsPushNotifications.swift
 | 
			
		||||
//  DHBW-Service
 | 
			
		||||
//
 | 
			
		||||
//  Created by Patrick Müller on 16.02.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct SettingsPushNotifications: View {
 | 
			
		||||
    @State private var notificationsEnabled = false
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack {
 | 
			
		||||
            Toggle(isOn: $notificationsEnabled) {
 | 
			
		||||
                Text("Receive push notifications")
 | 
			
		||||
            }
 | 
			
		||||
            .frame(maxWidth: 500, alignment: .center)
 | 
			
		||||
            
 | 
			
		||||
            Button(action: {
 | 
			
		||||
                scheduleTestNotification()
 | 
			
		||||
            }){
 | 
			
		||||
                Text("Test Notification")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .onChange(of: notificationsEnabled) { newVal in
 | 
			
		||||
            if(newVal) {
 | 
			
		||||
                enablePushService()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension SettingsPushNotifications {
 | 
			
		||||
    private func enablePushService() {
 | 
			
		||||
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (allowed, error) in
 | 
			
		||||
            //This callback does not trigger on main loop be careful
 | 
			
		||||
            if allowed {
 | 
			
		||||
                print("Allowed")
 | 
			
		||||
            
 | 
			
		||||
                // Get token
 | 
			
		||||
                DispatchQueue.main.async {
 | 
			
		||||
                    UIApplication.shared.registerForRemoteNotifications()
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                print("Error")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private func scheduleTestNotification() {
 | 
			
		||||
        let content = UNMutableNotificationContent()
 | 
			
		||||
        content.title = "Upcoming Exam"
 | 
			
		||||
        content.subtitle = "Your exam in theoretical computer science 3 is one week from now."
 | 
			
		||||
        content.sound = UNNotificationSound.default
 | 
			
		||||
 | 
			
		||||
        // show this notification five seconds from now
 | 
			
		||||
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
 | 
			
		||||
 | 
			
		||||
        // choose a random identifier
 | 
			
		||||
        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
 | 
			
		||||
 | 
			
		||||
        // add our notification request
 | 
			
		||||
        UNUserNotificationCenter.current().add(request)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct SettingsPushNotifications_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        SettingsPushNotifications()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user