10 Commits

Author SHA1 Message Date
Paddy 600f4c9c76 💄 Refactoring color management and extending to all views
- Thanks to @liza-kl for the color scheme <3
2021-07-08 18:27:49 +02:00
Paddy c49c5186e1 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-05-14 20:39:24 +02:00
Paddy 476e1caf9e 👥 Adding Lisa as contributor 2021-05-14 20:37:57 +02:00
Patrick 1431720850 Update README.md 2021-05-13 01:04:42 +02:00
Lisa be3582c19e Merge pull request #7 from Mueller-Patrick/colorextension
💄 Added a Color Extension with some rgb color codes for ligh…
2021-05-11 15:27:09 +02:00
li54 590e89fb1a 💄 Added a Color Extension with some rgb color codes for light and darkmode 2021-05-11 15:13:07 +02:00
Paddy 543bd190eb Some enhancements to the lecture plan list 2021-04-30 22:33:46 +02:00
Paddy f6e1d979fd 💄 Updating events list view 2021-04-07 21:15:22 +02:00
Paddy 06b66a50a5 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-07 13:27:00 +02:00
Paddy 02cd3a0db9 RaPla parser is now aware of recurring events
- Also some small improvements and basic implementation of Notifications
2021-04-07 13:26:37 +02:00
20 changed files with 688 additions and 97 deletions
+16
View File
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
42560FA0264AB7E40062053B /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42560F9F264AB7E40062053B /* ColorExtension.swift */; };
CD17FAD025C9F93B0088D700 /* HomeView.strings in Resources */ = {isa = PBXBuildFile; fileRef = CD17FAD225C9F93B0088D700 /* HomeView.strings */; };
CD2FC0C525A869FE00963178 /* dhbw-standard-icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD2FC0C025A869FE00963178 /* dhbw-standard-icon@2x.png */; };
CD2FC0C625A869FE00963178 /* dhbw-standard-icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD2FC0C125A869FE00963178 /* dhbw-standard-icon@3x.png */; };
@@ -16,6 +17,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 */; };
@@ -26,6 +28,7 @@
CD9FABA3258EC60600D6D0C5 /* DHBW_ServiceUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FABA2258EC60600D6D0C5 /* DHBW_ServiceUITests.swift */; };
CDA1CBAE25D4591000DB2AE5 /* User+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA1CBA825D4591000DB2AE5 /* User+CoreDataProperties.swift */; };
CDA1CBB025D4591000DB2AE5 /* Lecturer+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA1CBAA25D4591000DB2AE5 /* Lecturer+CoreDataProperties.swift */; };
CDB1E947261DDA0200EDE9EB /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB1E946261DDA0200EDE9EB /* DateExtension.swift */; };
CDCD721A25912E1200FBF2F5 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCD721925912E1200FBF2F5 /* HomeView.swift */; };
CDCD72242591316500FBF2F5 /* LocalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCD72232591316500FBF2F5 /* LocalSettings.swift */; };
CDCD7230259135C500FBF2F5 /* FirstOpeningSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCD722F259135C500FBF2F5 /* FirstOpeningSettings.swift */; };
@@ -59,6 +62,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
42560F9F264AB7E40062053B /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
CD17FAD125C9F93B0088D700 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/HomeView.strings; sourceTree = "<group>"; };
CD17FAD625C9F9440088D700 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/HomeView.strings; sourceTree = "<group>"; };
CD2FC0C025A869FE00963178 /* dhbw-standard-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dhbw-standard-icon@2x.png"; sourceTree = "<group>"; };
@@ -69,6 +73,7 @@
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>"; };
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>"; };
@@ -85,6 +90,7 @@
CD9FABA4258EC60600D6D0C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CDA1CBA825D4591000DB2AE5 /* User+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "User+CoreDataProperties.swift"; path = "DHBW-Service/CoreData/Models/User+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
CDA1CBAA25D4591000DB2AE5 /* Lecturer+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Lecturer+CoreDataProperties.swift"; path = "DHBW-Service/CoreData/Models/Lecturer+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
CDB1E946261DDA0200EDE9EB /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; };
CDCD721925912E1200FBF2F5 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
CDCD72232591316500FBF2F5 /* LocalSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalSettings.swift; sourceTree = "<group>"; };
CDCD722F259135C500FBF2F5 /* FirstOpeningSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstOpeningSettings.swift; sourceTree = "<group>"; };
@@ -179,6 +185,7 @@
isa = PBXGroup;
children = (
CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */,
CD9E3F0E25DC466100C77D10 /* SettingsPushNotifications.swift */,
);
path = SettingsSubViews;
sourceTree = "<group>";
@@ -316,6 +323,8 @@
CDDCF47A2591FE550027CDC5 /* UtilityFunctions.swift */,
CD8555BD25C47AE500C4ACD6 /* RaPlaFetcher.swift */,
CD8555C225C47B5300C4ACD6 /* ApiService.swift */,
CDB1E946261DDA0200EDE9EB /* DateExtension.swift */,
42560F9F264AB7E40062053B /* ColorExtension.swift */,
);
path = Utility;
sourceTree = "<group>";
@@ -471,16 +480,19 @@
CDDCF4842592028A0027CDC5 /* Localizer.swift in Sources */,
CDD970DF25D453D90061755E /* Lecturer+CoreDataClass.swift in Sources */,
CDCD7230259135C500FBF2F5 /* FirstOpeningSettings.swift in Sources */,
42560FA0264AB7E40062053B /* ColorExtension.swift in Sources */,
CDA1CBB025D4591000DB2AE5 /* Lecturer+CoreDataProperties.swift in Sources */,
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 */,
CD8555BE25C47AE500C4ACD6 /* RaPlaFetcher.swift in Sources */,
CDEA70C025C85999001CFE28 /* LecturePlanItem.swift in Sources */,
CD8555C325C47B5300C4ACD6 /* ApiService.swift in Sources */,
CDB1E947261DDA0200EDE9EB /* DateExtension.swift in Sources */,
CD9FAB8D258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld in Sources */,
CDCD721A25912E1200FBF2F5 /* HomeView.swift in Sources */,
CDD39B4B259A64150078D05F /* SettingsMain.swift in Sources */,
@@ -675,8 +687,10 @@
CD9FABA8258EC60600D6D0C5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.0.2;
DEVELOPMENT_ASSET_PATHS = "\"DHBW-Service/Preview Content\"";
DEVELOPMENT_TEAM = HS7KNT4MZ2;
ENABLE_PREVIEWS = YES;
@@ -696,8 +710,10 @@
CD9FABA9258EC60600D6D0C5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.0.2;
DEVELOPMENT_ASSET_PATHS = "\"DHBW-Service/Preview Content\"";
DEVELOPMENT_TEAM = HS7KNT4MZ2;
ENABLE_PREVIEWS = YES;
+20
View File
@@ -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>
+12
View File
@@ -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
View 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>
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "174",
"green" : "174",
"red" : "174"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.153",
"green" : "0.125",
"red" : "0.063"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "255",
"green" : "255",
"red" : "255"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "123",
"green" : "114",
"red" : "98"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "224",
"green" : "224",
"red" : "224"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "79",
"green" : "71",
"red" : "55"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
+11 -3
View File
@@ -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>
+13
View File
@@ -0,0 +1,13 @@
//
// ColorExtension.swift
// DHBW-Service
//
// Created by Lisa Kletsko on 11.05.21.
//
import SwiftUI
import Foundation
extension Color {
static let primaryColor = Color("PrimaryColor")
static let darkColor = Color("DarkColor")
static let lightColor = Color("LightColor")
}
+18
View File
@@ -0,0 +1,18 @@
//
// DateExtension.swift
// DHBW-Service
//
// Created by Patrick Müller on 07.04.21.
//
import Foundation
extension Date {
func get(_ components: Calendar.Component..., calendar: Calendar = Calendar.current) -> DateComponents {
return calendar.dateComponents(Set(components), from: self)
}
func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int {
return calendar.component(component, from: self)
}
}
+168 -13
View File
@@ -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,37 +221,127 @@ 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)
}
// (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.
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
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 {
// TODO: Delete all old lecturer objects
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 = ""
}
}
+9 -7
View File
@@ -33,7 +33,7 @@ struct HomeView: View {
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray)
.fill(Color.primaryColor)
)
Spacer()
}
@@ -41,7 +41,8 @@ struct HomeView: View {
Spacer()
VStack {
Text("information".localized(tableName: "HomeView", plural: false))
.font(.title3)
.font(.headline)
.fontWeight(.semibold)
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
VStack(alignment: .leading) {
@@ -59,7 +60,7 @@ struct HomeView: View {
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray)
.fill(Color.primaryColor)
)
Spacer()
}
@@ -228,7 +229,8 @@ struct UpcomingLecturesBlock: View {
var body: some View {
VStack {
Text(titleKey.localized(tableName: "HomeView"))
.font(.title)
.font(.title2)
.fontWeight(.semibold)
.frame(maxWidth: .infinity, alignment: .leading)
VStack {
if(!eventsList.isEmpty){
@@ -245,7 +247,7 @@ struct UpcomingLecturesBlock: View {
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray)
.fill(Color.primaryColor)
)
}
}
@@ -274,11 +276,11 @@ struct UpcomingExamsBlock: View {
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray)
.fill(Color.primaryColor)
)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.red, lineWidth: 4)
.stroke(Color("AccentColor"), lineWidth: 4)
)
}
}
@@ -46,7 +46,6 @@ struct LecturePlanItem: View {
Button(action: {
event.isHidden = !isHidden
self.isHidden = !isHidden
PersistenceController.shared.save()
}){
if(self.isHidden){
Text("Show")
@@ -55,7 +54,7 @@ struct LecturePlanItem: View {
}
}
.padding()
.foregroundColor(.white)
.foregroundColor(.primary)
.background(Color.blue)
.cornerRadius(15)
Spacer()
@@ -64,7 +63,7 @@ struct LecturePlanItem: View {
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray)
.fill(Color.primaryColor)
)
Spacer()
}
@@ -74,6 +73,9 @@ struct LecturePlanItem: View {
.onAppear{
self.isHidden = event.isHidden
}
.onDisappear{
PersistenceController.shared.save()
}
}
}
+160 -28
View File
@@ -7,40 +7,33 @@
import SwiftUI
import CoreData
import Combine
struct LecturePlanList: View {
@State private var events: [RaPlaEvent] = []
@State private var daysWithEvents: [Date:[RaPlaEvent]] = [:]
@State private var sortingAscending = true
var body: some View {
NavigationView() {
List {
ForEach(events, id: \.self) { event in
NavigationLink(destination: LecturePlanItem(event: event)){
ScrollView(.vertical) {
ScrollViewReader { scrollView in
// Button("Jump to today") {
// withAnimation(){
// scrollView.scrollTo(8, anchor: .center)
// }
// }
// .padding()
ForEach(daysWithEvents.sorted(by: {$0.key < $1.key}), id: \.key) { key, value in
HStack {
Text(formatDate(date: event.startDate!))
.foregroundColor(getEventForegroundColor(for: event))
Text(event.summary!)
.foregroundColor(getEventForegroundColor(for: event))
Spacer()
if(event.isHidden) {
Image(systemName: "eye.slash")
.foregroundColor(.red)
} else {
Image(systemName: "eye")
DayWithEventsBlock(date: key, eventsList: value, parent: self)
Spacer()
}
}
}
// When an event gets updated from child view, reload it here as this will not trigger the onAppear() function
.onReceive(event.objectWillChange, perform: { _ in
let sectionSortDescriptor = NSSortDescriptor(key: "startDate", ascending: true)
let sortDescriptors = [sectionSortDescriptor]
self.events = []
self.events = RaPlaEvent.getSpecified(sortDescriptors: sortDescriptors)
})
}
}
.navigationBarTitle(Text("Lectures"))
// .navigationBarItems(trailing: {
@@ -58,10 +51,8 @@ struct LecturePlanList: View {
}
.navigationViewStyle(StackNavigationViewStyle())
.onAppear{
let sectionSortDescriptor = NSSortDescriptor(key: "startDate", ascending: true)
let sortDescriptors = [sectionSortDescriptor]
self.events = []
self.events = RaPlaEvent.getSpecified(sortDescriptors: sortDescriptors)
self.getRaPlaEvents()
self.findNextDay()
}
}
}
@@ -83,13 +74,22 @@ struct LecturePlanList_Previews: PreviewProvider {
}
extension LecturePlanList {
private func formatDate(date: Date) -> String {
public func formatDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter.string(from: date)
}
private func getEventForegroundColor(for event: RaPlaEvent) -> Color {
public func formatTime(date: Date) -> String {
let formatter = DateFormatter()
formatter.timeStyle = .short
return formatter.string(from: date)
}
/**
Get the correct foreground color for the given event, e.g. primary for normal events and red for exams.
*/
public func getEventForegroundColor(for event: RaPlaEvent) -> Color {
var textColor: Color = .primary
if(event.category! == "Prüfung") {
textColor = Color.red
@@ -99,4 +99,136 @@ extension LecturePlanList {
return textColor
}
/**
DEPRECATED, used to find the element in the list of days that represents the next day
*/
public func findNextDay() {
// As this list is already sorted ascending, we can just return the first event with a start date that is in the future
let sortedEvents = self.events.sorted(by: { $0.startDate! < $1.startDate! })
for event in sortedEvents {
if(event.startDate! > Date()) {
//self.focusedEvent = event
return
}
}
}
/**
Loads required data from CoreData
*/
public func getRaPlaEvents() {
let sectionSortDescriptor = NSSortDescriptor(key: "startDate", ascending: true)
let sortDescriptors = [sectionSortDescriptor]
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
let dateFrom = calendar.startOfDay(for: Date())
let fromPredicate = NSPredicate(format: "startDate >= %@", dateFrom as NSDate)
self.events = []
self.daysWithEvents = [:]
self.events = RaPlaEvent.getSpecified(sortDescriptors: sortDescriptors, searchPredicate: fromPredicate)
// Also write events to daysWithEvents map
for event in self.events {
let components = event.startDate!.get(.day, .month, .year)
let day = String(components.day!); let month = String(components.month!); let year = String(components.year!)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let dayOnly = dateFormatter.date(from: String(year + "-" + month + "-" + day))!
var eventsList = daysWithEvents[dayOnly]
if(eventsList == nil) {
eventsList = []
}
eventsList!.append(event)
self.daysWithEvents[dayOnly] = eventsList
}
}
public func updateDay(day: Date, events: [RaPlaEvent]) {
self.daysWithEvents[day] = events
}
}
/**
Each of these represents one day block in the view
*/
struct DayWithEventsBlock: View {
@State var date: Date
@State var eventsList: [RaPlaEvent]
@State var parent: LecturePlanList
var body: some View {
VStack {
Text(String(date.get(.day)) + "." + String(date.get(.month)) + "." + String(date.get(.year)))
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
VStack {
if(!eventsList.isEmpty){
ForEach(eventsList, id: \.self) { event in
var visibleIconGroup = Group {
Button(action: {
event.isHidden.toggle()
}){
if(event.isHidden) {
Image(systemName: "eye.slash")
.foregroundColor(.red)
} else {
Image(systemName: "eye")
}
}
}
NavigationLink(destination: LecturePlanItem(event: event)) {
HStack {
Text(parent.formatTime(date: event.startDate!))
.foregroundColor(parent.getEventForegroundColor(for: event))
Text(event.summary!)
.foregroundColor(parent.getEventForegroundColor(for: event))
Spacer()
visibleIconGroup
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.darkColor)
)
}
// When an event gets updated from child view, reload it here as this will not trigger the onAppear() function
.onReceive(event.objectWillChange, perform: { _ in
print("receiving event: \(event.isHidden)")
visibleIconGroup = Group {
Button(action: {
event.isHidden.toggle()
}){
if(event.isHidden) {
Image(systemName: "eye.slash")
.foregroundColor(.red)
} else {
Image(systemName: "eye")
}
}
}
// parent.updateDay(day: self.date, events: self.eventsList)
})
}
} else {
Text("noLectures".localized(tableName: "HomeView"))
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.primaryColor)
)
}
}
@@ -20,6 +20,11 @@ struct SettingsMain: View {
label: {
Text("Acknowledgements")
})
NavigationLink(
destination: SettingsPushNotifications(),
label: {
Text("Push Notifications")
})
Button(action: {
self.showLogoutConfirmationAlert = true
}, label: {
@@ -13,8 +13,9 @@ struct SettingsAcknowledgements: View {
Text("contributors".localized(tableName: "General", plural: false))
.font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/)
Spacer()
Text("David Huh")
Text("Patrick Müller")
Text("David Huh (davidhuh.de)")
Text("Lisa Kletsko (li54.de)")
Text("Patrick Müller (mueller-patrick.tech)")
Spacer()
}
}
@@ -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()
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
# DHBW-Service-App
## Introduction
This is a project by two students of the Cooperative State University in Karlsruhe (DHBW Karlsruhe).
This is a project by three students of the Cooperative State University in Karlsruhe (DHBW Karlsruhe).
The goal is to develop a native iOS app providing easy and straightforward access to services like RaPla, Dualis and the canteen plan.
<b>Disclaimer:</b> This project is not affiliated with the DHBW Karlsruhe