21 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
Patrick d7f078cc88 Update issue templates 2021-02-17 11:18:09 +01:00
Patrick 805495fb81 Create README.md 2021-02-12 21:57:54 +01:00
Paddy f5756cd650 🐛 Fixed bug where the app would create new RaPlaEvent objects instead of updating the existing ones 2021-02-10 22:20:10 +01:00
Paddy ba0d4da657 Adding lecturer to lecture plan item detail view 2021-02-10 21:27:26 +01:00
Paddy b689a7bbf1 ♻️ Refactored CoreData access 2021-02-10 20:26:53 +01:00
Paddy f50ba8f19d :saprkles: Adding CoreData Helper Classes
- Also adding lecturer entity and relationship between event and lecturers.
- Preparation for CoreData refactoring
2021-02-10 19:17:11 +01:00
David Huh e5ecb6bb60 💄 Changed Lecture Item 2021-02-07 21:17:08 +01:00
David Huh a3871f87da 💄 Changed LectureItem 2021-02-07 20:39:56 +01:00
Paddy 5e177eedd5 Merge branch 'ui-refactor' 2021-02-07 16:14:11 +01:00
Paddy 797046ddcc Merge remote-tracking branch 'refs/remotes/origin/main' 2021-02-07 16:14:05 +01:00
David Huh 3323dcac5a 💄 Added frame for Information of user 2021-02-07 16:13:37 +01:00
31 changed files with 1279 additions and 182 deletions
+37
View File
@@ -0,0 +1,37 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Device Information:**
- Device: [e.g. iPhone 12]
- OS: [e.g. iOS 14.1]
- App Version [e.g. 1.0]
**Beta Information:**
If you are participating in the TestFlight Beta program, please provide the following information:
- Build No.: [e.g. 1.0.12]
- Have you already submitted feedback via TestFlight? [yes/no]
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[REQ]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+48
View File
@@ -7,14 +7,17 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; 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 */; }; 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 */; }; CD2FC0C625A869FE00963178 /* dhbw-standard-icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD2FC0C125A869FE00963178 /* dhbw-standard-icon@3x.png */; };
CD2FC0C725A869FE00963178 /* alpaca-alt-icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD2FC0C325A869FE00963178 /* alpaca-alt-icon@2x.png */; }; CD2FC0C725A869FE00963178 /* alpaca-alt-icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD2FC0C325A869FE00963178 /* alpaca-alt-icon@2x.png */; };
CD2FC0C825A869FE00963178 /* alpaca-alt-icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD2FC0C425A869FE00963178 /* alpaca-alt-icon@3x.png */; }; CD2FC0C825A869FE00963178 /* alpaca-alt-icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD2FC0C425A869FE00963178 /* alpaca-alt-icon@3x.png */; };
CD67FB2A25D45A3300069CDB /* RaPlaEvent+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD67FB2925D45A3300069CDB /* RaPlaEvent+CoreDataProperties.swift */; };
CD730A35259A860E00E0BB69 /* SettingsAcknowledgements.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */; }; CD730A35259A860E00E0BB69 /* SettingsAcknowledgements.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */; };
CD8555BE25C47AE500C4ACD6 /* RaPlaFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8555BD25C47AE500C4ACD6 /* RaPlaFetcher.swift */; }; CD8555BE25C47AE500C4ACD6 /* RaPlaFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8555BD25C47AE500C4ACD6 /* RaPlaFetcher.swift */; };
CD8555C325C47B5300C4ACD6 /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8555C225C47B5300C4ACD6 /* ApiService.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 */; }; CD9FAB81258EC60200D6D0C5 /* DHBW_ServiceApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB80258EC60200D6D0C5 /* DHBW_ServiceApp.swift */; };
CD9FAB83258EC60200D6D0C5 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB82258EC60200D6D0C5 /* ContentView.swift */; }; CD9FAB83258EC60200D6D0C5 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB82258EC60200D6D0C5 /* ContentView.swift */; };
CD9FAB85258EC60600D6D0C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD9FAB84258EC60600D6D0C5 /* Assets.xcassets */; }; CD9FAB85258EC60600D6D0C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD9FAB84258EC60600D6D0C5 /* Assets.xcassets */; };
@@ -23,10 +26,16 @@
CD9FAB8D258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB8B258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld */; }; CD9FAB8D258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB8B258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld */; };
CD9FAB98258EC60600D6D0C5 /* DHBW_ServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB97258EC60600D6D0C5 /* DHBW_ServiceTests.swift */; }; CD9FAB98258EC60600D6D0C5 /* DHBW_ServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FAB97258EC60600D6D0C5 /* DHBW_ServiceTests.swift */; };
CD9FABA3258EC60600D6D0C5 /* DHBW_ServiceUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9FABA2258EC60600D6D0C5 /* DHBW_ServiceUITests.swift */; }; 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 */; }; CDCD721A25912E1200FBF2F5 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCD721925912E1200FBF2F5 /* HomeView.swift */; };
CDCD72242591316500FBF2F5 /* LocalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCD72232591316500FBF2F5 /* LocalSettings.swift */; }; CDCD72242591316500FBF2F5 /* LocalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCD72232591316500FBF2F5 /* LocalSettings.swift */; };
CDCD7230259135C500FBF2F5 /* FirstOpeningSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCD722F259135C500FBF2F5 /* FirstOpeningSettings.swift */; }; CDCD7230259135C500FBF2F5 /* FirstOpeningSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCD722F259135C500FBF2F5 /* FirstOpeningSettings.swift */; };
CDD39B4B259A64150078D05F /* SettingsMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD39B4A259A64150078D05F /* SettingsMain.swift */; }; CDD39B4B259A64150078D05F /* SettingsMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD39B4A259A64150078D05F /* SettingsMain.swift */; };
CDD970DD25D453D90061755E /* User+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD970D725D453D90061755E /* User+CoreDataClass.swift */; };
CDD970DF25D453D90061755E /* Lecturer+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD970D925D453D90061755E /* Lecturer+CoreDataClass.swift */; };
CDD970E125D453D90061755E /* RaPlaEvent+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD970DB25D453D90061755E /* RaPlaEvent+CoreDataClass.swift */; };
CDDCF47B2591FE550027CDC5 /* UtilityFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDCF47A2591FE550027CDC5 /* UtilityFunctions.swift */; }; CDDCF47B2591FE550027CDC5 /* UtilityFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDCF47A2591FE550027CDC5 /* UtilityFunctions.swift */; };
CDDCF4842592028A0027CDC5 /* Localizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDCF4832592028A0027CDC5 /* Localizer.swift */; }; CDDCF4842592028A0027CDC5 /* Localizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDCF4832592028A0027CDC5 /* Localizer.swift */; };
CDDCF493259203390027CDC5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CDDCF495259203390027CDC5 /* Localizable.strings */; }; CDDCF493259203390027CDC5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CDDCF495259203390027CDC5 /* Localizable.strings */; };
@@ -53,15 +62,18 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference 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>"; }; 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>"; }; 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>"; }; CD2FC0C025A869FE00963178 /* dhbw-standard-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dhbw-standard-icon@2x.png"; sourceTree = "<group>"; };
CD2FC0C125A869FE00963178 /* dhbw-standard-icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dhbw-standard-icon@3x.png"; sourceTree = "<group>"; }; CD2FC0C125A869FE00963178 /* dhbw-standard-icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dhbw-standard-icon@3x.png"; sourceTree = "<group>"; };
CD2FC0C325A869FE00963178 /* alpaca-alt-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "alpaca-alt-icon@2x.png"; sourceTree = "<group>"; }; CD2FC0C325A869FE00963178 /* alpaca-alt-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "alpaca-alt-icon@2x.png"; sourceTree = "<group>"; };
CD2FC0C425A869FE00963178 /* alpaca-alt-icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "alpaca-alt-icon@3x.png"; sourceTree = "<group>"; }; CD2FC0C425A869FE00963178 /* alpaca-alt-icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "alpaca-alt-icon@3x.png"; sourceTree = "<group>"; };
CD67FB2925D45A3300069CDB /* RaPlaEvent+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RaPlaEvent+CoreDataProperties.swift"; sourceTree = "<group>"; };
CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAcknowledgements.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; CD9FAB82258EC60200D6D0C5 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -76,10 +88,16 @@
CD9FAB9E258EC60600D6D0C5 /* DHBW-ServiceUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DHBW-ServiceUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; CD9FAB9E258EC60600D6D0C5 /* DHBW-ServiceUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DHBW-ServiceUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
CD9FABA2258EC60600D6D0C5 /* DHBW_ServiceUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DHBW_ServiceUITests.swift; sourceTree = "<group>"; }; CD9FABA2258EC60600D6D0C5 /* DHBW_ServiceUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DHBW_ServiceUITests.swift; sourceTree = "<group>"; };
CD9FABA4258EC60600D6D0C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; CDCD722F259135C500FBF2F5 /* FirstOpeningSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstOpeningSettings.swift; sourceTree = "<group>"; };
CDD39B4A259A64150078D05F /* SettingsMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMain.swift; sourceTree = "<group>"; }; CDD39B4A259A64150078D05F /* SettingsMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMain.swift; sourceTree = "<group>"; };
CDD970D725D453D90061755E /* User+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "User+CoreDataClass.swift"; sourceTree = "<group>"; };
CDD970D925D453D90061755E /* Lecturer+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Lecturer+CoreDataClass.swift"; sourceTree = "<group>"; };
CDD970DB25D453D90061755E /* RaPlaEvent+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RaPlaEvent+CoreDataClass.swift"; sourceTree = "<group>"; };
CDDCF47A2591FE550027CDC5 /* UtilityFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityFunctions.swift; sourceTree = "<group>"; }; CDDCF47A2591FE550027CDC5 /* UtilityFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityFunctions.swift; sourceTree = "<group>"; };
CDDCF4832592028A0027CDC5 /* Localizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localizer.swift; sourceTree = "<group>"; }; CDDCF4832592028A0027CDC5 /* Localizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localizer.swift; sourceTree = "<group>"; };
CDDCF494259203390027CDC5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; }; CDDCF494259203390027CDC5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -115,6 +133,19 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
CD1ED26325D33B590030B647 /* Models */ = {
isa = PBXGroup;
children = (
CDD970D925D453D90061755E /* Lecturer+CoreDataClass.swift */,
CDA1CBAA25D4591000DB2AE5 /* Lecturer+CoreDataProperties.swift */,
CDD970DB25D453D90061755E /* RaPlaEvent+CoreDataClass.swift */,
CD67FB2925D45A3300069CDB /* RaPlaEvent+CoreDataProperties.swift */,
CDD970D725D453D90061755E /* User+CoreDataClass.swift */,
CDA1CBA825D4591000DB2AE5 /* User+CoreDataProperties.swift */,
);
path = Models;
sourceTree = "<group>";
};
CD2FC0BD25A869F000963178 /* res */ = { CD2FC0BD25A869F000963178 /* res */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -154,6 +185,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */, CD730A34259A860E00E0BB69 /* SettingsAcknowledgements.swift */,
CD9E3F0E25DC466100C77D10 /* SettingsPushNotifications.swift */,
); );
path = SettingsSubViews; path = SettingsSubViews;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -239,6 +271,7 @@
CDCD721125912D5400FBF2F5 /* CoreData */ = { CDCD721125912D5400FBF2F5 /* CoreData */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CD1ED26325D33B590030B647 /* Models */,
CD9FAB8B258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld */, CD9FAB8B258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld */,
CD9FAB89258EC60600D6D0C5 /* Persistence.swift */, CD9FAB89258EC60600D6D0C5 /* Persistence.swift */,
); );
@@ -290,6 +323,8 @@
CDDCF47A2591FE550027CDC5 /* UtilityFunctions.swift */, CDDCF47A2591FE550027CDC5 /* UtilityFunctions.swift */,
CD8555BD25C47AE500C4ACD6 /* RaPlaFetcher.swift */, CD8555BD25C47AE500C4ACD6 /* RaPlaFetcher.swift */,
CD8555C225C47B5300C4ACD6 /* ApiService.swift */, CD8555C225C47B5300C4ACD6 /* ApiService.swift */,
CDB1E946261DDA0200EDE9EB /* DateExtension.swift */,
42560F9F264AB7E40062053B /* ColorExtension.swift */,
); );
path = Utility; path = Utility;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -443,19 +478,28 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
CDDCF4842592028A0027CDC5 /* Localizer.swift in Sources */, CDDCF4842592028A0027CDC5 /* Localizer.swift in Sources */,
CDD970DF25D453D90061755E /* Lecturer+CoreDataClass.swift in Sources */,
CDCD7230259135C500FBF2F5 /* FirstOpeningSettings.swift in Sources */, CDCD7230259135C500FBF2F5 /* FirstOpeningSettings.swift in Sources */,
42560FA0264AB7E40062053B /* ColorExtension.swift in Sources */,
CDA1CBB025D4591000DB2AE5 /* Lecturer+CoreDataProperties.swift in Sources */,
CD9FAB8A258EC60600D6D0C5 /* Persistence.swift in Sources */, CD9FAB8A258EC60600D6D0C5 /* Persistence.swift in Sources */,
CD730A35259A860E00E0BB69 /* SettingsAcknowledgements.swift in Sources */, CD730A35259A860E00E0BB69 /* SettingsAcknowledgements.swift in Sources */,
CD9FAB83258EC60200D6D0C5 /* ContentView.swift in Sources */, CD9FAB83258EC60200D6D0C5 /* ContentView.swift in Sources */,
CD9E3F0F25DC466100C77D10 /* SettingsPushNotifications.swift in Sources */,
CDCD72242591316500FBF2F5 /* LocalSettings.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 */, CD8555BE25C47AE500C4ACD6 /* RaPlaFetcher.swift in Sources */,
CDEA70C025C85999001CFE28 /* LecturePlanItem.swift in Sources */, CDEA70C025C85999001CFE28 /* LecturePlanItem.swift in Sources */,
CD8555C325C47B5300C4ACD6 /* ApiService.swift in Sources */, CD8555C325C47B5300C4ACD6 /* ApiService.swift in Sources */,
CDB1E947261DDA0200EDE9EB /* DateExtension.swift in Sources */,
CD9FAB8D258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld in Sources */, CD9FAB8D258EC60600D6D0C5 /* DHBW_Service.xcdatamodeld in Sources */,
CDCD721A25912E1200FBF2F5 /* HomeView.swift in Sources */, CDCD721A25912E1200FBF2F5 /* HomeView.swift in Sources */,
CDD39B4B259A64150078D05F /* SettingsMain.swift in Sources */, CDD39B4B259A64150078D05F /* SettingsMain.swift in Sources */,
CD67FB2A25D45A3300069CDB /* RaPlaEvent+CoreDataProperties.swift in Sources */,
CDEA70B225C6054F001CFE28 /* LecturePlanList.swift in Sources */, CDEA70B225C6054F001CFE28 /* LecturePlanList.swift in Sources */,
CDDCF47B2591FE550027CDC5 /* UtilityFunctions.swift in Sources */, CDDCF47B2591FE550027CDC5 /* UtilityFunctions.swift in Sources */,
CDA1CBAE25D4591000DB2AE5 /* User+CoreDataProperties.swift in Sources */,
CD9FAB81258EC60200D6D0C5 /* DHBW_ServiceApp.swift in Sources */, CD9FAB81258EC60200D6D0C5 /* DHBW_ServiceApp.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -643,8 +687,10 @@
CD9FABA8258EC60600D6D0C5 /* Debug */ = { CD9FABA8258EC60600D6D0C5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.0.2;
DEVELOPMENT_ASSET_PATHS = "\"DHBW-Service/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"DHBW-Service/Preview Content\"";
DEVELOPMENT_TEAM = HS7KNT4MZ2; DEVELOPMENT_TEAM = HS7KNT4MZ2;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -664,8 +710,10 @@
CD9FABA9258EC60600D6D0C5 /* Release */ = { CD9FABA9258EC60600D6D0C5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.0.2;
DEVELOPMENT_ASSET_PATHS = "\"DHBW-Service/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"DHBW-Service/Preview Content\"";
DEVELOPMENT_TEAM = HS7KNT4MZ2; DEVELOPMENT_TEAM = HS7KNT4MZ2;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
+21 -1
View File
@@ -6,12 +6,14 @@
// //
import SwiftUI import SwiftUI
import UserNotifications
@main @main
struct DHBW_ServiceApp: App { struct DHBW_ServiceApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared
let settings = LocalSettings() let settings = LocalSettings()
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() 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,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?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="RaPlaEvent" representedClassName="RaPlaEvent" syncable="YES" codeGenerationType="class"> <entity name="Lecturer" representedClassName="Lecturer" syncable="YES">
<attribute name="email" optional="YES" attributeType="String"/>
<attribute name="name" optional="YES" attributeType="String"/>
<relationship name="event" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RaPlaEvent" inverseName="lecturers" inverseEntity="RaPlaEvent"/>
</entity>
<entity name="RaPlaEvent" representedClassName="RaPlaEvent" syncable="YES">
<attribute name="category" optional="YES" attributeType="String"/> <attribute name="category" optional="YES" attributeType="String"/>
<attribute name="descr" optional="YES" attributeType="String"/> <attribute name="descr" optional="YES" attributeType="String"/>
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/> <attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
@@ -9,14 +14,17 @@
<attribute name="startDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/> <attribute name="startDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="summary" optional="YES" attributeType="String"/> <attribute name="summary" optional="YES" attributeType="String"/>
<attribute name="uid" optional="YES" attributeType="String"/> <attribute name="uid" optional="YES" attributeType="String"/>
<relationship name="lecturers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Lecturer" inverseName="event" inverseEntity="Lecturer"/>
</entity> </entity>
<entity name="User" representedClassName="User" syncable="YES" codeGenerationType="class"> <entity name="User" representedClassName="User" syncable="YES">
<attribute name="course" optional="YES" attributeType="String"/> <attribute name="course" optional="YES" attributeType="String"/>
<attribute name="director" optional="YES" attributeType="String"/> <attribute name="director" optional="YES" attributeType="String"/>
<attribute name="name" optional="YES" attributeType="String"/> <attribute name="name" optional="YES" attributeType="String"/>
<attribute name="raplaLink" optional="YES" attributeType="String"/>
</entity> </entity>
<elements> <elements>
<element name="RaPlaEvent" positionX="-63" positionY="9" width="128" height="149"/> <element name="Lecturer" positionX="-351" positionY="99" width="128" height="74"/>
<element name="User" positionX="-63" positionY="-9" 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="89"/>
</elements> </elements>
</model> </model>
@@ -0,0 +1,54 @@
//
// Lecturer+CoreDataClass.swift
// DHBW-Service
//
// Created by Patrick Müller on 10.02.21.
//
//
import Foundation
import CoreData
@objc(Lecturer)
public class Lecturer: NSManagedObject {
// MARK: Access methods
@nonobjc public class func getAll() -> [Lecturer] {
let managedContext =
PersistenceController.shared.context
do {
return try managedContext.fetch(Lecturer.fetchRequest())
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return []
}
}
@nonobjc public class func getSpecified(sortDescriptors: [NSSortDescriptor] = [], searchPredicate: NSPredicate? = nil) -> [Lecturer]{
let managedContext =
PersistenceController.shared.context
let fetchRequest: NSFetchRequest = Lecturer.fetchRequest()
fetchRequest.sortDescriptors = sortDescriptors
if(searchPredicate != nil) {
fetchRequest.predicate = searchPredicate
}
do {
return try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return []
}
}
// MARK: Wrappers
public var wrappedName: String {
name ?? ""
}
public var wrappedEmail: String {
email ?? ""
}
}
@@ -0,0 +1,27 @@
//
// Lecturer+CoreDataProperties.swift
// DHBW-Service
//
// Created by Patrick Müller on 10.02.21.
//
//
import Foundation
import CoreData
extension Lecturer {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Lecturer> {
return NSFetchRequest<Lecturer>(entityName: "Lecturer")
}
@NSManaged public var email: String?
@NSManaged public var name: String?
@NSManaged public var event: RaPlaEvent?
}
extension Lecturer : Identifiable {
}
@@ -0,0 +1,54 @@
//
// RaPlaEvent+CoreDataClass.swift
// DHBW-Service
//
// Created by Patrick Müller on 10.02.21.
//
//
import Foundation
import CoreData
@objc(RaPlaEvent)
public class RaPlaEvent: NSManagedObject {
// MARK: Access methods
@nonobjc public class func getAll() -> [RaPlaEvent] {
let managedContext =
PersistenceController.shared.context
do {
return try managedContext.fetch(RaPlaEvent.fetchRequest())
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return []
}
}
@nonobjc public class func getSpecified(sortDescriptors: [NSSortDescriptor] = [], searchPredicate: NSPredicate? = nil) -> [RaPlaEvent]{
let managedContext =
PersistenceController.shared.context
let fetchRequest: NSFetchRequest = RaPlaEvent.fetchRequest()
fetchRequest.sortDescriptors = sortDescriptors
if(searchPredicate != nil) {
fetchRequest.predicate = searchPredicate
}
do {
return try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return []
}
}
// MARK: Wrappers
public var lecturerList: [Lecturer] {
let set = lecturers as? Set<Lecturer> ?? []
return set.sorted {
$0.wrappedName < $1.wrappedName
}
}
}
@@ -0,0 +1,50 @@
//
// RaPlaEvent+CoreDataProperties.swift
// DHBW-Service
//
// Created by Patrick Müller on 10.02.21.
//
//
import Foundation
import CoreData
extension RaPlaEvent {
@nonobjc public class func fetchRequest() -> NSFetchRequest<RaPlaEvent> {
return NSFetchRequest<RaPlaEvent>(entityName: "RaPlaEvent")
}
@NSManaged public var category: String?
@NSManaged public var descr: String?
@NSManaged public var endDate: Date?
@NSManaged public var isHidden: Bool
@NSManaged public var location: String?
@NSManaged public var startDate: Date?
@NSManaged public var summary: String?
@NSManaged public var uid: String?
@NSManaged public var lecturers: NSSet?
}
// MARK: Generated accessors for lecturers
extension RaPlaEvent {
@objc(addLecturersObject:)
@NSManaged public func addToLecturers(_ value: Lecturer)
@objc(removeLecturersObject:)
@NSManaged public func removeFromLecturers(_ value: Lecturer)
@objc(addLecturers:)
@NSManaged public func addToLecturers(_ values: NSSet)
@objc(removeLecturers:)
@NSManaged public func removeFromLecturers(_ values: NSSet)
}
extension RaPlaEvent : Identifiable {
}
@@ -0,0 +1,44 @@
//
// User+CoreDataClass.swift
// DHBW-Service
//
// Created by Patrick Müller on 10.02.21.
//
//
import Foundation
import CoreData
@objc(User)
public class User: NSManagedObject {
@nonobjc public class func getAll() -> [User] {
let managedContext =
PersistenceController.shared.context
do {
return try managedContext.fetch(User.fetchRequest())
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return []
}
}
@nonobjc public class func getSpecified(sortDescriptors: [NSSortDescriptor] = [], searchPredicate: NSPredicate? = nil) -> [User]{
let managedContext =
PersistenceController.shared.context
let fetchRequest: NSFetchRequest = User.fetchRequest()
fetchRequest.sortDescriptors = sortDescriptors
if(searchPredicate != nil) {
fetchRequest.predicate = searchPredicate
}
do {
return try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return []
}
}
}
@@ -0,0 +1,27 @@
//
// User+CoreDataProperties.swift
// DHBW-Service
//
// Created by Patrick Müller on 10.02.21.
//
//
import Foundation
import CoreData
extension User {
@nonobjc public class func fetchRequest() -> NSFetchRequest<User> {
return NSFetchRequest<User>(entityName: "User")
}
@NSManaged public var course: String?
@NSManaged public var director: String?
@NSManaged public var name: String?
}
extension User : Identifiable {
}
+55 -40
View File
@@ -10,7 +10,7 @@ import CoreData
struct PersistenceController { struct PersistenceController {
// Singleton // Singleton
static let shared = PersistenceController() static let shared = PersistenceController()
// Cloud Kit container // Cloud Kit container
let container: NSPersistentCloudKitContainer let container: NSPersistentCloudKitContainer
@@ -20,7 +20,7 @@ struct PersistenceController {
return self.container.viewContext return self.container.viewContext
} }
} }
// MARK: - Constructor // MARK: - Constructor
init(inMemory: Bool = false) { init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "DHBW_Service") container = NSPersistentCloudKitContainer(name: "DHBW_Service")
@@ -31,15 +31,15 @@ struct PersistenceController {
if let error = error as NSError? { if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately. // 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. // 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: Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing. * 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 persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space. * The device is out of space.
* The store could not be migrated to the current model version. * The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was. Check the error message to determine what the actual problem was.
*/ */
fatalError("Unresolved error \(error), \(error.userInfo)") fatalError("Unresolved error \(error), \(error.userInfo)")
} }
}) })
@@ -65,39 +65,54 @@ struct PersistenceController {
let viewContext = result.container.viewContext let viewContext = result.container.viewContext
// set mock user // set mock user
let userEntity = NSEntityDescription.entity(forEntityName: "User", in: PersistenceController.shared.context)! let user = User(context: PersistenceController.shared.context)
let user = NSManagedObject(entity: userEntity, insertInto: PersistenceController.shared.context) user.name = "Max Mustermann"
user.setValue("Max Mustermann", forKey: "name") user.course = "TINF19B4"
user.setValue("TINF19B4", forKey: "course") user.director = "Prof. Dr. Mustermann"
user.setValue("Dr. Mustermann", forKey: "director")
// Generate mock events // Generate mock events
let eventEntity = NSEntityDescription.entity(forEntityName: "RaPlaEvent", in: PersistenceController.shared.context)! let normalEvent1 = RaPlaEvent(context: PersistenceController.shared.context)
let normalEvent1 = NSManagedObject(entity: eventEntity, insertInto: PersistenceController.shared.context) let normalEvent2 = RaPlaEvent(context: PersistenceController.shared.context)
let normalEvent2 = NSManagedObject(entity: eventEntity, insertInto: PersistenceController.shared.context) let examEvent = RaPlaEvent(context: PersistenceController.shared.context)
let examEvent = NSManagedObject(entity: eventEntity, insertInto: PersistenceController.shared.context) normalEvent1.summary = "Mock Event 1"
normalEvent1.setValue("Mock Event 1", forKey: "summary") normalEvent2.summary = "Mock Event 2"
normalEvent2.setValue("Mock Event 2", forKey: "summary") examEvent.summary = "Exam Event"
examEvent.setValue("Exam Event", forKey: "summary")
normalEvent1.setValue("Mock Event 1 Description", forKey: "descr") normalEvent1.descr = "Mock Event 1 description"
normalEvent2.setValue("Mock Event 2 Description", forKey: "descr") normalEvent2.descr = "Mock Event 2 description"
examEvent.setValue("Exam Event Description", forKey: "descr") examEvent.descr = "Exam Event description"
normalEvent1.setValue("E207 INF Hörsaal", forKey: "location")
normalEvent2.setValue("A306 WI Hörsaal", forKey: "location") normalEvent1.location = "E207 INF Hörsaal"
examEvent.setValue("Audimax A", forKey: "location") normalEvent2.location = "A306 WI Hörsaal"
normalEvent1.setValue("Lehrveranstaltung", forKey: "category") examEvent.location = "Audimax A"
normalEvent2.setValue("Lehrveranstaltung", forKey: "category")
examEvent.setValue("Prüfung", forKey: "category") normalEvent1.category = "Lehrveranstaltung"
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() var currentDate = Date()
currentDate.addTimeInterval(1*60*60);normalEvent1.setValue(currentDate, forKey: "startDate") currentDate.addTimeInterval(1*60*60);normalEvent1.startDate = currentDate
currentDate.addTimeInterval(1*60*60);normalEvent2.setValue(currentDate, forKey: "startDate") currentDate.addTimeInterval(1*60*60);normalEvent2.startDate = currentDate
currentDate.addTimeInterval(1*60*60);examEvent.setValue(currentDate, forKey: "startDate") currentDate.addTimeInterval(1*60*60);examEvent.startDate = currentDate
currentDate.addTimeInterval(1*60*60);normalEvent1.setValue(currentDate, forKey: "endDate") currentDate.addTimeInterval(1*60*60);normalEvent1.endDate = currentDate
currentDate.addTimeInterval(1*60*60);normalEvent2.setValue(currentDate, forKey: "endDate") currentDate.addTimeInterval(1*60*60);normalEvent2.endDate = currentDate
currentDate.addTimeInterval(1*60*60);examEvent.setValue(currentDate, forKey: "endDate") currentDate.addTimeInterval(1*60*60);examEvent.endDate = currentDate
normalEvent1.setValue("totalUniqueId1", forKey: "uid")
normalEvent2.setValue("totalUniqueId2", forKey: "uid") normalEvent1.uid = "totalUniqueId1"
examEvent.setValue("totalUniqueId3", forKey: "uid") normalEvent2.uid = "totalUniqueId2"
examEvent.uid = "totalUniqueId3"
do { do {
+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
}
}
+33 -25
View File
@@ -6,6 +6,36 @@
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>Alpaca-Alt-Icon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>alpaca-alt-icon</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
</dict>
<key>CFBundleIcons~ipad</key>
<dict>
<key>CFBundleAlternateIcons</key>
<dict>
<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> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
@@ -17,7 +47,9 @@
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UIApplicationSceneManifest</key> <key>UIApplicationSceneManifest</key>
@@ -44,29 +76,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>dhbw-standard-icon</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>Alpaca-Alt-Icon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>alpaca-alt-icon</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
</dict>
</dict> </dict>
</plist> </plist>
@@ -7,6 +7,7 @@
*/ */
"hey" = "Hey 👋🏻"; "hey" = "Hey 👋🏻";
"information" = "Deine Informationen";
"today" = "Heute"; "today" = "Heute";
"tomorrow" = "Morgen"; "tomorrow" = "Morgen";
"upcomingExams" = "Nächste Klausuren"; "upcomingExams" = "Nächste Klausuren";
@@ -7,6 +7,7 @@
*/ */
"hey" = "Hey 👋🏻"; "hey" = "Hey 👋🏻";
"information" = "Your Information";
"today" = "Today"; "today" = "Today";
"tomorrow" = "Tomorrow"; "tomorrow" = "Tomorrow";
"upcomingExams" = "Upcoming exams"; "upcomingExams" = "Upcoming exams";
+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)
}
}
+238 -28
View File
@@ -10,13 +10,26 @@ import CoreData
class RaPlaFetcher { class RaPlaFetcher {
public class iCalEvent { public class iCalEvent {
var startDate: Date = Date() //DTSTART var startDate: Date = Date() // DTSTART
var endDate: Date = Date() //DTEND var endDate: Date = Date() // DTEND
var summary: String = "" //SUMMARY var summary: String = "" // SUMMARY
var description: String = "" //DESCRIPTION var description: String = "" // DESCRIPTION
var location: String = "" //LOCATION var location: String = "" // LOCATION
var category: String = "" //CATEGORIES var category: String = "" // CATEGORIES
var uid: String = "" //UID 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 {
var name: String = ""
var email: String = ""
} }
// Get the RaPla file from the given URL and save the events to CoreData // Get the RaPla file from the given URL and save the events to CoreData
@@ -68,9 +81,31 @@ class RaPlaFetcher {
var events: [iCalEvent] = [] var events: [iCalEvent] = []
for eventString in eventStrings { for eventString in eventStrings {
let lines = eventString.components(separatedBy: .newlines) var lines = eventString.components(separatedBy: .newlines)
// Remove all blank lines that somehow are generated by the .components function
lines = removeBlankLines(lines: lines)
let evt = iCalEvent() let evt = iCalEvent()
// Iterate over all lines and merge lines that have been split by rapla first
for lineNr in 0...lines.count-1 {
if(lines[lineNr].hasPrefix(" ")){
lines[lineNr] = String(lines[lineNr].dropFirst())
// 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] = ""
}
}
// Remove all blank lines again as we produced some while merging the lines
lines = removeBlankLines(lines: lines)
for line in lines { for line in lines {
var lineWithoutPrefix = line var lineWithoutPrefix = line
if(!line.contains(":")) { if(!line.contains(":")) {
@@ -82,7 +117,7 @@ class RaPlaFetcher {
//Date format: 20181101T080000 //Date format: 20181101T080000
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
if(lineWithoutPrefix.contains("Z")){ if(lineWithoutPrefix.contains("Z")){
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmssZ" dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
} else { } else {
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss" dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss"
} }
@@ -91,7 +126,7 @@ class RaPlaFetcher {
} else if(line.hasPrefix("DTEND")){ } else if(line.hasPrefix("DTEND")){
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
if(lineWithoutPrefix.contains("Z")){ if(lineWithoutPrefix.contains("Z")){
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmssZ" dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
} else { } else {
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss" dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss"
} }
@@ -107,6 +142,73 @@ class RaPlaFetcher {
evt.category = lineWithoutPrefix evt.category = lineWithoutPrefix
} else if(line.hasPrefix("UID")){ } else if(line.hasPrefix("UID")){
evt.uid = lineWithoutPrefix evt.uid = lineWithoutPrefix
} else if(line.hasPrefix("ATTENDEE;ROLE=REQ-PARTICIPANT;")) {
var lecturerName = line
let begin = lecturerName.firstIndex(of: "\"")!
lecturerName.removeSubrange(lecturerName.startIndex...begin)
let end = lecturerName.lastIndex(of: "\"")!
lecturerName = String(lecturerName[..<end])
let lecturerEmail = String(lineWithoutPrefix[String.Index(utf16Offset: 7, in: lineWithoutPrefix)..<lineWithoutPrefix.endIndex])
let lecturer = LecturerObj()
lecturer.name = lecturerName
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)
}
} }
} }
@@ -119,32 +221,127 @@ class RaPlaFetcher {
// Save the given iCalEvent objects to CoreData // Save the given iCalEvent objects to CoreData
// Updates the events if they already exist and deletes old (/invalid) ones // Updates the events if they already exist and deletes old (/invalid) ones
private class func saveToCoreData(eventObjects: [iCalEvent]) -> Bool{ private class func saveToCoreData(eventObjects: [iCalEvent]) -> Bool{
let existingEvents = UtilityFunctions.getCoreDataObject(entity: "RaPlaEvent", sortDescriptors: []) // Get known UIDs
var existingEventsDict: [String:NSManagedObject] = [:] let existingEvents: [RaPlaEvent] = RaPlaEvent.getAll()
var existingEventsDict: [String:RaPlaEvent] = [:]
for event in existingEvents { for event in existingEvents {
existingEventsDict[event.value(forKey: "uid") as! String] = event existingEventsDict[event.uid!] = event
} }
let newEventUIDs = eventObjects.map{$0.uid} // List for new UIDs
var newEventUIDs: [String] = []
for event in eventObjects { for event in eventObjects {
// If the event already exists locally, update it. Otherwise, create a new record // If the event already exists locally, update it. Otherwise, create a new record
let evt: NSManagedObject if(event.isRecurring){
if existingEventsDict.keys.contains(event.uid) { // Create as many events as we need
evt = existingEventsDict[event.uid]! // 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(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 { } else {
let entity = NSEntityDescription.entity(forEntityName: "RaPlaEvent", in: PersistenceController.shared.context)! // Generate UID
evt = NSManagedObject(entity: entity, insertInto: PersistenceController.shared.context) let newUID = event.uid + "---0" // Appending ---0 to distinguish between recurring events
// Set default values for new object // Create or update existing CoreData object
evt.setValue(false, forKey: "isHidden") 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)
} }
evt.setValue(event.startDate, forKey: "startDate")
evt.setValue(event.endDate, forKey: "endDate")
evt.setValue(event.summary, forKey: "summary")
evt.setValue(event.description, forKey: "descr")
evt.setValue(event.location, forKey: "location")
evt.setValue(event.category, forKey: "category")
evt.setValue(event.uid, forKey: "uid")
} }
// Now we also have to delete locally stored events that have been deleted from RaPla // Now we also have to delete locally stored events that have been deleted from RaPla
@@ -160,4 +357,17 @@ class RaPlaFetcher {
return true return true
} }
private class func removeBlankLines(lines: [String]) -> [String] {
var newLines = lines
// Remove all blank lines that somehow are generated by the .components function
for line in newLines {
if(line.isEmpty){
newLines.remove(at: newLines.firstIndex(of: line)!)
}
}
return newLines
}
} }
@@ -9,6 +9,7 @@ import Foundation
import CoreData import CoreData
class UtilityFunctions { class UtilityFunctions {
// DEPRECATED, replaced by the respective getSpecified() method of each CoreData object
public class func getCoreDataObject(entity: String, sortDescriptors: [NSSortDescriptor] = [], searchPredicate: NSPredicate? = nil) -> [NSManagedObject]{ public class func getCoreDataObject(entity: String, sortDescriptors: [NSSortDescriptor] = [], searchPredicate: NSPredicate? = nil) -> [NSManagedObject]{
let managedContext = let managedContext =
PersistenceController.shared.context PersistenceController.shared.context
@@ -11,8 +11,10 @@ import CoreData
struct FirstOpeningSettings: View { struct FirstOpeningSettings: View {
@EnvironmentObject var settings: LocalSettings @EnvironmentObject var settings: LocalSettings
@State private var name = "" @State private var name = ""
@State private var dhbwLocation = ""
@State private var course = "" @State private var course = ""
@State private var director = "" @State private var director = ""
@State private var raplaLink = ""
@State private var invalidInputName = false @State private var invalidInputName = false
@State private var invalidInputCourse = 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) .frame(minWidth: 200, idealWidth: nil, maxWidth: 500, minHeight: nil, idealHeight: nil, maxHeight: nil, alignment: .center)
.padding(.horizontal) .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) TextField("course".localized(tableName: "General"), text: self.$course)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(invalidInputCourse ? Color.red : Color.secondary, lineWidth: 1)) .overlay(RoundedRectangle(cornerRadius: 10).stroke(invalidInputCourse ? Color.red : Color.secondary, lineWidth: 1))
.onChange(of: course, perform: { value in .onChange(of: course, perform: { value in
self.setDirector() self.setCourseInfo()
self.course = self.course.uppercased() self.course = self.course.uppercased()
}) })
.foregroundColor(invalidInputCourse ? .red : .primary) .foregroundColor(invalidInputCourse ? .red : .primary)
@@ -65,11 +75,13 @@ struct FirstOpeningSettings: View {
} }
extension FirstOpeningSettings{ extension FirstOpeningSettings{
func setDirector() { func setCourseInfo() {
if (course == "TINF19B4") { // TODO: Replace this with some database query or stuff like this to load actual data
director = "Jörn Eisenbiegler" switch course {
} else { case "TINF19B4":
director = "" director = "Jörn Eisenbiegler"
default:
director = ""
} }
} }
@@ -96,11 +108,10 @@ extension FirstOpeningSettings{
print("Deleting old user data status: \(status)") print("Deleting old user data status: \(status)")
// Insert new user data // Insert new user data
let entity = NSEntityDescription.entity(forEntityName: "User", in: PersistenceController.shared.context)! let user = User(context: PersistenceController.shared.context)
let user = NSManagedObject(entity: entity, insertInto: PersistenceController.shared.context) user.name = name
user.setValue(name, forKey: "name") user.course = course
user.setValue(course, forKey: "course") user.director = director
user.setValue(director, forKey: "director")
self.settings.isFirstOpening = !self.settings.isFirstOpening self.settings.isFirstOpening = !self.settings.isFirstOpening
PersistenceController.shared.save() PersistenceController.shared.save()
+50 -22
View File
@@ -13,9 +13,9 @@ struct HomeView: View {
@State private var name: String = "" @State private var name: String = ""
@State private var course: String = "" @State private var course: String = ""
@State private var director: String = "" @State private var director: String = ""
@State private var todaysEvents: [NSManagedObject] = [] @State private var todaysEvents: [RaPlaEvent] = []
@State private var tomorrowsEvents: [NSManagedObject] = [] @State private var tomorrowsEvents: [RaPlaEvent] = []
@State private var upcomingExams: [NSManagedObject] = [] @State private var upcomingExams: [RaPlaEvent] = []
var body: some View { var body: some View {
NavigationView { NavigationView {
@@ -33,7 +33,34 @@ struct HomeView: View {
.padding() .padding()
.background( .background(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
.fill(Color.gray) .fill(Color.primaryColor)
)
Spacer()
}
HStack {
Spacer()
VStack {
Text("information".localized(tableName: "HomeView", plural: false))
.font(.headline)
.fontWeight(.semibold)
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
VStack(alignment: .leading) {
Text("course".localized(tableName: "General", plural: false) + ": ")
Text("director".localized(tableName: "General", plural: false) + ": ")
}
VStack(alignment: .leading) {
Text(self.course)
.bold()
Text(self.director)
.bold()
}.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.primaryColor)
) )
Spacer() Spacer()
} }
@@ -89,25 +116,25 @@ struct HomeView: View {
extension HomeView{ extension HomeView{
// Read required data from CoreData and save it to the appropriate variables // Read required data from CoreData and save it to the appropriate variables
func readFromCoreData() { func readFromCoreData() {
let fetchedData = UtilityFunctions.getCoreDataObject(entity: "User") let fetchedData = User.getAll()
if(!fetchedData.isEmpty) { if(!fetchedData.isEmpty) {
let user = fetchedData[0] let user = fetchedData[0]
self.name = user.value(forKey: "name") as! String self.name = user.name!
self.course = user.value(forKey: "course") as! String self.course = user.course!
self.director = user.value(forKey: "director") as! String self.director = user.director!
} }
} }
// Get 0...2 of todays lectures from RaPla // Get 0...2 of todays lectures from RaPla
// Returns a list of RaPlaEvent NSManagedObjects // Returns a list of RaPlaEvent NSManagedObjects
func getTodaysEvents() -> [NSManagedObject] { func getTodaysEvents() -> [RaPlaEvent] {
let searchPredicate = NSPredicate(format: "(category == 'Lehrveranstaltung')") let searchPredicate = NSPredicate(format: "(category == 'Lehrveranstaltung')")
let hiddenPredicate = NSPredicate(format: "isHidden == NO") let hiddenPredicate = NSPredicate(format: "isHidden == NO")
var predicates = [searchPredicate, hiddenPredicate] var predicates = [searchPredicate, hiddenPredicate]
predicates.append(contentsOf: getDayPredicates(today: true)) predicates.append(contentsOf: getDayPredicates(today: true))
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates) let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
let events = UtilityFunctions.getCoreDataObject(entity: "RaPlaEvent", searchPredicate: compoundPredicate) let events = RaPlaEvent.getSpecified(searchPredicate: compoundPredicate)
if(!events.isEmpty) { if(!events.isEmpty) {
return Array(events[...min(1, events.count-1)]) return Array(events[...min(1, events.count-1)])
} else { } else {
@@ -117,13 +144,13 @@ extension HomeView{
// Get 0...2 of tomorrows lectures from RaPla // Get 0...2 of tomorrows lectures from RaPla
// Returns a list of RaPlaEvent NSManagedObjects // Returns a list of RaPlaEvent NSManagedObjects
func getTomorrowsEvents() -> [NSManagedObject] { func getTomorrowsEvents() -> [RaPlaEvent] {
let searchPredicate = NSPredicate(format: "(category == 'Lehrveranstaltung')") let searchPredicate = NSPredicate(format: "(category == 'Lehrveranstaltung')")
let hiddenPredicate = NSPredicate(format: "isHidden == NO") let hiddenPredicate = NSPredicate(format: "isHidden == NO")
var predicates = [searchPredicate, hiddenPredicate] var predicates = [searchPredicate, hiddenPredicate]
predicates.append(contentsOf: getDayPredicates(tomorrow: true)) predicates.append(contentsOf: getDayPredicates(tomorrow: true))
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates) let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
let events = UtilityFunctions.getCoreDataObject(entity: "RaPlaEvent", searchPredicate: compoundPredicate) let events = RaPlaEvent.getSpecified(searchPredicate: compoundPredicate)
if(!events.isEmpty) { if(!events.isEmpty) {
return Array(events[...min(1, events.count-1)]) return Array(events[...min(1, events.count-1)])
} else { } else {
@@ -133,13 +160,13 @@ extension HomeView{
// Get 0...2 of upcoming exams from RaPla // Get 0...2 of upcoming exams from RaPla
// Returns a list of RaPlaEvent NSManagedObjects // Returns a list of RaPlaEvent NSManagedObjects
func getUpcomingExams() -> [NSManagedObject] { func getUpcomingExams() -> [RaPlaEvent] {
let searchPredicate = NSPredicate(format: "category == %@", "Prüfung") let searchPredicate = NSPredicate(format: "category == %@", "Prüfung")
let hiddenPredicate = NSPredicate(format: "isHidden == NO") let hiddenPredicate = NSPredicate(format: "isHidden == NO")
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [searchPredicate, hiddenPredicate]) let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [searchPredicate, hiddenPredicate])
let sectionSortDescriptor = NSSortDescriptor(key: "startDate", ascending: true) let sectionSortDescriptor = NSSortDescriptor(key: "startDate", ascending: true)
let sortDescriptors = [sectionSortDescriptor] let sortDescriptors = [sectionSortDescriptor]
let events = UtilityFunctions.getCoreDataObject(entity: "RaPlaEvent", sortDescriptors: sortDescriptors, searchPredicate: compoundPredicate) let events = RaPlaEvent.getSpecified(sortDescriptors: sortDescriptors, searchPredicate: compoundPredicate)
if(!events.isEmpty) { if(!events.isEmpty) {
return Array(events[...min(1, events.count-1)]) return Array(events[...min(1, events.count-1)])
} else { } else {
@@ -196,18 +223,19 @@ extension HomeView{
} }
struct UpcomingLecturesBlock: View { struct UpcomingLecturesBlock: View {
let eventsList: [NSManagedObject] let eventsList: [RaPlaEvent]
let titleKey: String let titleKey: String
var body: some View { var body: some View {
VStack { VStack {
Text(titleKey.localized(tableName: "HomeView")) Text(titleKey.localized(tableName: "HomeView"))
.font(.title) .font(.title2)
.fontWeight(.semibold)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
VStack { VStack {
if(!eventsList.isEmpty){ if(!eventsList.isEmpty){
ForEach(eventsList, id: \.self) { exam in ForEach(eventsList, id: \.self) { exam in
Text(exam.value(forKey: "summary") as! String) Text(exam.summary!)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
} else { } else {
@@ -219,13 +247,13 @@ struct UpcomingLecturesBlock: View {
.padding() .padding()
.background( .background(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
.fill(Color.gray) .fill(Color.primaryColor)
) )
} }
} }
struct UpcomingExamsBlock: View { struct UpcomingExamsBlock: View {
let examsList: [NSManagedObject] let examsList: [RaPlaEvent]
let titleKey: String let titleKey: String
var body: some View { var body: some View {
@@ -236,7 +264,7 @@ struct UpcomingExamsBlock: View {
VStack { VStack {
if(!examsList.isEmpty){ if(!examsList.isEmpty){
ForEach(examsList, id: \.self) { exam in ForEach(examsList, id: \.self) { exam in
Text(exam.value(forKey: "summary") as! String) Text(exam.summary!)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
} else { } else {
@@ -248,11 +276,11 @@ struct UpcomingExamsBlock: View {
.padding() .padding()
.background( .background(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
.fill(Color.gray) .fill(Color.primaryColor)
) )
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
.stroke(Color.red, lineWidth: 4) .stroke(Color("AccentColor"), lineWidth: 4)
) )
} }
} }
+75 -18
View File
@@ -9,34 +9,91 @@ import SwiftUI
import CoreData import CoreData
struct LecturePlanItem: View { struct LecturePlanItem: View {
@State var event: NSManagedObject @State var event: RaPlaEvent
@State var isHidden = false @State var isHidden = false
var body: some View { var body: some View {
VStack { VStack {
Text(event.value(forKey: "summary") as! String) HStack {
Button(action: { Spacer()
event.setValue(!isHidden, forKey: "isHidden") VStack {
self.isHidden = !isHidden Text(event.summary!)
PersistenceController.shared.save() .font(.title3)
}){ .frame(maxWidth: .infinity, alignment: .leading)
if(self.isHidden){ Text(event.descr!)
Text("Show") .bold()
} else { .frame(maxWidth: .infinity, alignment: .leading)
Text("Hide") Divider()
HStack {
VStack(alignment: .leading) {
Text("When")
Text("Where")
Text("Who")
}
VStack(alignment: .leading) {
Text(getDateAndTimeAsString(date: event.startDate!)
+ " to "
+ getTimeAsString(date: event.endDate!))
.bold()
Text(event.location!)
.bold()
Text(!event.lecturerList.isEmpty ? event.lecturerList[0].wrappedName : "")
.bold()
}.frame(maxWidth: .infinity, alignment: .leading)
}
Divider()
HStack {
Button(action: {
event.isHidden = !isHidden
self.isHidden = !isHidden
}){
if(self.isHidden){
Text("Show")
} else {
Text("Hide")
}
}
.padding()
.foregroundColor(.primary)
.background(Color.blue)
.cornerRadius(15)
Spacer()
}
} }
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.primaryColor)
)
Spacer()
} }
.padding() .frame(maxWidth: .infinity)
.foregroundColor(.white) Spacer()
.background(Color.blue)
.cornerRadius(15)
} }
.onAppear{ .onAppear{
self.isHidden = event.value(forKey: "isHidden") as! Bool self.isHidden = event.isHidden
}
.onDisappear{
PersistenceController.shared.save()
} }
} }
} }
func getDateAndTimeAsString(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter.string(from: date)
}
func getTimeAsString(date: Date) -> String {
let formatter = DateFormatter()
formatter.timeStyle = .short
return formatter.string(from: date)
}
struct LecturePlanItem_Previews: PreviewProvider { struct LecturePlanItem_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
LecturePlanItem(event: getPreviewEvent()) LecturePlanItem(event: getPreviewEvent())
@@ -52,7 +109,7 @@ struct LecturePlanItem_Previews: PreviewProvider {
return settings return settings
} }
static func getPreviewEvent() -> NSManagedObject { static func getPreviewEvent() -> RaPlaEvent {
return UtilityFunctions.getCoreDataObject(entity: "RaPlaEvent", sortDescriptors: [])[0] return RaPlaEvent.getSpecified(sortDescriptors: [])[0]
} }
} }
+162 -30
View File
@@ -7,39 +7,32 @@
import SwiftUI import SwiftUI
import CoreData import CoreData
import Combine
struct LecturePlanList: View { struct LecturePlanList: View {
@State private var events: [NSManagedObject] = [] @State private var events: [RaPlaEvent] = []
@State private var daysWithEvents: [Date:[RaPlaEvent]] = [:]
@State private var sortingAscending = true @State private var sortingAscending = true
var body: some View { var body: some View {
NavigationView() { NavigationView() {
List { ScrollView(.vertical) {
ForEach(events, id: \.self) { event in ScrollViewReader { scrollView in
NavigationLink(destination: LecturePlanItem(event: event)){ // 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 { HStack {
Text(formatDate(date: event.value(forKeyPath: "startDate") as! Date))
.foregroundColor(getEventForegroundColor(for: event))
Text(event.value(forKeyPath: "summary") as! String)
.foregroundColor(getEventForegroundColor(for: event))
Spacer() Spacer()
DayWithEventsBlock(date: key, eventsList: value, parent: self)
if(event.value(forKey: "isHidden") as! Bool) { Spacer()
Image(systemName: "eye.slash")
.foregroundColor(.red)
} else {
Image(systemName: "eye")
}
} }
} }
// 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 = UtilityFunctions.getCoreDataObject(entity: "RaPlaEvent", sortDescriptors: sortDescriptors)
})
} }
} }
.navigationBarTitle(Text("Lectures")) .navigationBarTitle(Text("Lectures"))
@@ -58,10 +51,8 @@ struct LecturePlanList: View {
} }
.navigationViewStyle(StackNavigationViewStyle()) .navigationViewStyle(StackNavigationViewStyle())
.onAppear{ .onAppear{
let sectionSortDescriptor = NSSortDescriptor(key: "startDate", ascending: true) self.getRaPlaEvents()
let sortDescriptors = [sectionSortDescriptor] self.findNextDay()
self.events = []
self.events = UtilityFunctions.getCoreDataObject(entity: "RaPlaEvent", sortDescriptors: sortDescriptors)
} }
} }
} }
@@ -83,15 +74,24 @@ struct LecturePlanList_Previews: PreviewProvider {
} }
extension LecturePlanList { extension LecturePlanList {
private func formatDate(date: Date) -> String { public func formatDate(date: Date) -> String {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateStyle = .short formatter.dateStyle = .short
return formatter.string(from: date) return formatter.string(from: date)
} }
private func getEventForegroundColor(for event: NSManagedObject) -> 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 var textColor: Color = .primary
if(event.value(forKeyPath: "category") as! String == "Prüfung") { if(event.category! == "Prüfung") {
textColor = Color.red textColor = Color.red
} else { } else {
textColor = Color.primary textColor = Color.primary
@@ -99,4 +99,136 @@ extension LecturePlanList {
return textColor 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: { label: {
Text("Acknowledgements") Text("Acknowledgements")
}) })
NavigationLink(
destination: SettingsPushNotifications(),
label: {
Text("Push Notifications")
})
Button(action: { Button(action: {
self.showLogoutConfirmationAlert = true self.showLogoutConfirmationAlert = true
}, label: { }, label: {
@@ -13,8 +13,9 @@ struct SettingsAcknowledgements: View {
Text("contributors".localized(tableName: "General", plural: false)) Text("contributors".localized(tableName: "General", plural: false))
.font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/) .font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/)
Spacer() Spacer()
Text("David Huh") Text("David Huh (davidhuh.de)")
Text("Patrick Müller") Text("Lisa Kletsko (li54.de)")
Text("Patrick Müller (mueller-patrick.tech)")
Spacer() 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()
}
}
+13
View File
@@ -0,0 +1,13 @@
# DHBW-Service-App
## Introduction
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
## Build it yourself
As the app is not yet available on the App Store, you can build and install it yourself if you have a Mac with Xcode installed.
Just clone the repository, then open the project in Xcode and navigate to the project settings. In the target section, select
the iOS target. Then you have to select your own development team in the dropdown or create a new one.
If you completed these steps, you should be able to install a development version of the app on your own devices.