mirror of
https://github.com/Mueller-Patrick/DHBW-Service-App.git
synced 2024-12-22 05:15:13 +00:00
✨ RaPla parser is now aware of recurring events
- Also some small improvements and basic implementation of Notifications
This commit is contained in:
parent
805495fb81
commit
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,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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
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,14 +20,20 @@
|
|||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
</dict>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
<dict>
|
||||
<key>CFBundleAlternateIcons</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>dhbw-standard-icon</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<false/>
|
||||
<key>Alpaca-Alt-Icon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>alpaca-alt-icon</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundleIdentifier</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>
|
||||
|
|
|
@ -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..<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,36 +221,124 @@ 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
|
||||
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..<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(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 {
|
||||
evt = RaPlaEvent(context: PersistenceController.shared.context)
|
||||
// Generate UID
|
||||
let newUID = event.uid + "---0" // Appending ---0 to distinguish between recurring events
|
||||
|
||||
// Set default values for new object
|
||||
evt.isHidden = false
|
||||
}
|
||||
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
|
||||
for lecturer in event.lecturers {
|
||||
let lect = Lecturer(context: PersistenceController.shared.context)
|
||||
lect.name = lecturer.name
|
||||
lect.email = lecturer.email
|
||||
lect.event = evt
|
||||
// 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 = 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,11 +75,13 @@ struct FirstOpeningSettings: View {
|
|||
}
|
||||
|
||||
extension FirstOpeningSettings{
|
||||
func setDirector() {
|
||||
if (course == "TINF19B4") {
|
||||
director = "Jörn Eisenbiegler"
|
||||
} else {
|
||||
director = ""
|
||||
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"
|
||||
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