mirror of
https://github.com/Mueller-Patrick/DHBW-Service-App.git
synced 2026-05-26 12:38:05 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1431720850 | |||
| be3582c19e | |||
| 590e89fb1a | |||
| 543bd190eb | |||
| f6e1d979fd | |||
| 06b66a50a5 | |||
| 02cd3a0db9 | |||
| d7f078cc88 | |||
| 805495fb81 | |||
| f5756cd650 | |||
| ba0d4da657 |
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
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 */; };
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
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 */; };
|
||||||
@@ -26,6 +28,7 @@
|
|||||||
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 */; };
|
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 */; };
|
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 */; };
|
||||||
@@ -59,6 +62,7 @@
|
|||||||
/* 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>"; };
|
||||||
@@ -69,6 +73,7 @@
|
|||||||
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>"; };
|
||||||
@@ -85,6 +90,7 @@
|
|||||||
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; };
|
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; };
|
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>"; };
|
||||||
@@ -179,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>";
|
||||||
@@ -316,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>";
|
||||||
@@ -471,16 +480,19 @@
|
|||||||
CDDCF4842592028A0027CDC5 /* Localizer.swift in Sources */,
|
CDDCF4842592028A0027CDC5 /* Localizer.swift in Sources */,
|
||||||
CDD970DF25D453D90061755E /* Lecturer+CoreDataClass.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 */,
|
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 */,
|
CDD970E125D453D90061755E /* RaPlaEvent+CoreDataClass.swift in Sources */,
|
||||||
CDD970DD25D453D90061755E /* User+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 */,
|
||||||
@@ -675,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;
|
||||||
@@ -696,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;
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
@@ -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"?>
|
<?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">
|
<entity name="Lecturer" representedClassName="Lecturer" syncable="YES">
|
||||||
<attribute name="email" optional="YES" attributeType="String"/>
|
<attribute name="email" optional="YES" attributeType="String"/>
|
||||||
<attribute name="name" optional="YES" attributeType="String"/>
|
<attribute name="name" optional="YES" attributeType="String"/>
|
||||||
@@ -20,10 +20,11 @@
|
|||||||
<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="Lecturer" positionX="-351" positionY="99" width="128" height="74"/>
|
<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="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>
|
</elements>
|
||||||
</model>
|
</model>
|
||||||
@@ -11,6 +11,7 @@ import CoreData
|
|||||||
|
|
||||||
@objc(Lecturer)
|
@objc(Lecturer)
|
||||||
public class Lecturer: NSManagedObject {
|
public class Lecturer: NSManagedObject {
|
||||||
|
// MARK: Access methods
|
||||||
@nonobjc public class func getAll() -> [Lecturer] {
|
@nonobjc public class func getAll() -> [Lecturer] {
|
||||||
let managedContext =
|
let managedContext =
|
||||||
PersistenceController.shared.context
|
PersistenceController.shared.context
|
||||||
@@ -41,4 +42,13 @@ public class Lecturer: NSManagedObject {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Wrappers
|
||||||
|
public var wrappedName: String {
|
||||||
|
name ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
public var wrappedEmail: String {
|
||||||
|
email ?? ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import CoreData
|
|||||||
|
|
||||||
@objc(RaPlaEvent)
|
@objc(RaPlaEvent)
|
||||||
public class RaPlaEvent: NSManagedObject {
|
public class RaPlaEvent: NSManagedObject {
|
||||||
|
|
||||||
|
// MARK: Access methods
|
||||||
@nonobjc public class func getAll() -> [RaPlaEvent] {
|
@nonobjc public class func getAll() -> [RaPlaEvent] {
|
||||||
let managedContext =
|
let managedContext =
|
||||||
PersistenceController.shared.context
|
PersistenceController.shared.context
|
||||||
@@ -41,4 +43,12 @@ public class RaPlaEvent: NSManagedObject {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Wrappers
|
||||||
|
public var lecturerList: [Lecturer] {
|
||||||
|
let set = lecturers as? Set<Lecturer> ?? []
|
||||||
|
return set.sorted {
|
||||||
|
$0.wrappedName < $1.wrappedName
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,13 +33,13 @@ struct PersistenceController {
|
|||||||
// 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)")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -90,6 +90,18 @@ struct PersistenceController {
|
|||||||
normalEvent2.category = "Lehrveranstaltung"
|
normalEvent2.category = "Lehrveranstaltung"
|
||||||
examEvent.category = "Prüfung"
|
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.startDate = currentDate
|
currentDate.addTimeInterval(1*60*60);normalEvent1.startDate = currentDate
|
||||||
currentDate.addTimeInterval(1*60*60);normalEvent2.startDate = currentDate
|
currentDate.addTimeInterval(1*60*60);normalEvent2.startDate = currentDate
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>aps-environment</key>
|
||||||
|
<string>development</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -20,14 +20,20 @@
|
|||||||
<false/>
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>CFBundlePrimaryIcon</key>
|
</dict>
|
||||||
|
<key>CFBundleIcons~ipad</key>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleAlternateIcons</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleIconFiles</key>
|
<key>Alpaca-Alt-Icon</key>
|
||||||
<array>
|
<dict>
|
||||||
<string>dhbw-standard-icon</string>
|
<key>CFBundleIconFiles</key>
|
||||||
</array>
|
<array>
|
||||||
<key>UIPrerenderedIcon</key>
|
<string>alpaca-alt-icon</string>
|
||||||
<false/>
|
</array>
|
||||||
|
<key>UIPrerenderedIcon</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -41,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>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// ColorExtension.swift
|
||||||
|
// DHBW-Service
|
||||||
|
//
|
||||||
|
// Created by Lisa Kletsko on 11.05.21.
|
||||||
|
//
|
||||||
|
import SwiftUI
|
||||||
|
import Foundation
|
||||||
|
extension Color {
|
||||||
|
// For Darkmode
|
||||||
|
static let darkModeDarkColor = Color(red: 16 / 255, green: 32 / 255, blue: 39 / 255)
|
||||||
|
static let darkModePrimaryColor = Color(red: 55 / 255, green: 71 / 255, blue: 79 / 255)
|
||||||
|
static let darkModeLightColor = Color(red: 98 / 255, green: 114 / 255, blue: 123 / 255)
|
||||||
|
// For Lightmode
|
||||||
|
static let lightkModeDarkColor = Color(red: 174 / 255, green: 174 / 255, blue: 174 / 255)
|
||||||
|
static let lightModePrimaryColor = Color(red: 224 / 255, green: 224 / 255, blue: 224 / 255)
|
||||||
|
static let lightModeLightColor = Color(red: 255 / 255, green: 255 / 255, blue: 255 / 255)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,14 +10,21 @@ 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 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 {
|
public class LecturerObj {
|
||||||
@@ -84,7 +91,14 @@ class RaPlaFetcher {
|
|||||||
for lineNr in 0...lines.count-1 {
|
for lineNr in 0...lines.count-1 {
|
||||||
if(lines[lineNr].hasPrefix(" ")){
|
if(lines[lineNr].hasPrefix(" ")){
|
||||||
lines[lineNr] = String(lines[lineNr].dropFirst())
|
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] = ""
|
lines[lineNr] = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,6 +158,57 @@ class RaPlaFetcher {
|
|||||||
lecturer.email = lecturerEmail
|
lecturer.email = lecturerEmail
|
||||||
|
|
||||||
evt.lecturers.append(lecturer)
|
evt.lecturers.append(lecturer)
|
||||||
|
} else if(line.hasPrefix("RRULE")) {
|
||||||
|
// This line normally looks like this: RRULE:FREQ=WEEKLY;COUNT=12;INTERVAL=1;BYDAY=TU
|
||||||
|
// Can also look smth like this though: RRULE:FREQ=WEEKLY;COUNT=12
|
||||||
|
|
||||||
|
evt.isRecurring = true
|
||||||
|
|
||||||
|
let params = lineWithoutPrefix.components(separatedBy: ";")
|
||||||
|
|
||||||
|
for param in params {
|
||||||
|
let keyword = param[param.startIndex..<param.firstIndex(of: "=")!]
|
||||||
|
let value = param[param.index(param.firstIndex(of: "=")!, offsetBy: 1)..<param.endIndex]
|
||||||
|
|
||||||
|
switch keyword {
|
||||||
|
case "FREQ":
|
||||||
|
evt.frequency = String(value)
|
||||||
|
break
|
||||||
|
case "COUNT":
|
||||||
|
evt.recCount = Int(value)!
|
||||||
|
break
|
||||||
|
case "INTERVAL":
|
||||||
|
evt.recInterval = Int(value)!
|
||||||
|
break
|
||||||
|
case "BYDAY":
|
||||||
|
evt.recDay = String(value)
|
||||||
|
break
|
||||||
|
case "UNTIL":
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateFormat = "yyyyMMdd"
|
||||||
|
let date = dateFormatter.date(from: String(value))!
|
||||||
|
evt.recUntil = date
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
print("Unknown parameter found: " + line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (line.hasPrefix("EXDATE")) {
|
||||||
|
// Excluded from recurring events
|
||||||
|
// Format: 20210401T133000,20210408T133000,20210513T133000
|
||||||
|
|
||||||
|
let exclDateStrings = lineWithoutPrefix.components(separatedBy: ",")
|
||||||
|
|
||||||
|
for exclDate in exclDateStrings {
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
if(lineWithoutPrefix.contains("Z")){
|
||||||
|
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
|
||||||
|
} else {
|
||||||
|
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss"
|
||||||
|
}
|
||||||
|
let date = dateFormatter.date(from: exclDate)!
|
||||||
|
evt.excludedDates.append(date)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,36 +221,126 @@ 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: [RaPlaEvent] = [] //RaPlaEvent.getAll()
|
// Get known UIDs
|
||||||
|
let existingEvents: [RaPlaEvent] = RaPlaEvent.getAll()
|
||||||
var existingEventsDict: [String:RaPlaEvent] = [:]
|
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: RaPlaEvent
|
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
|
||||||
} else {
|
for iteration in 0..<event.recCount {
|
||||||
evt = RaPlaEvent(context: PersistenceController.shared.context)
|
// Calculate start- and enddate
|
||||||
|
// Calculate offset
|
||||||
|
let offsetType: Calendar.Component
|
||||||
|
let offsetAmount: Int
|
||||||
|
|
||||||
// Set default values for new object
|
switch event.frequency {
|
||||||
evt.isHidden = false
|
case "DAILY":
|
||||||
}
|
offsetType = Calendar.Component.day
|
||||||
evt.startDate = event.startDate
|
offsetAmount = 1
|
||||||
evt.endDate = event.endDate
|
break
|
||||||
evt.summary = event.summary
|
case "WEEKLY":
|
||||||
evt.descr = event.description
|
offsetType = Calendar.Component.day
|
||||||
evt.location = event.location
|
offsetAmount = 7
|
||||||
evt.category = event.category
|
break
|
||||||
evt.uid = event.uid
|
case "MONTHLY":
|
||||||
for lecturer in event.lecturers {
|
offsetType = Calendar.Component.month
|
||||||
let lect = Lecturer(context: PersistenceController.shared.context)
|
offsetAmount = 1
|
||||||
lect.name = lecturer.name
|
break
|
||||||
lect.email = lecturer.email
|
case "YEARLY":
|
||||||
lect.event = evt
|
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 {
|
||||||
|
// 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 = 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.onAppear{
|
.onAppear{
|
||||||
// Called upon the opening of the app
|
// Called upon the opening of the app
|
||||||
RaPlaFetcher.getRaplaFileAndSaveToCoreData(from: "https://rapla.dhbw-karlsruhe.de/rapla?page=ical&user=eisenbiegler&file=TINF20B4")
|
RaPlaFetcher.getRaplaFileAndSaveToCoreData(from: "https://rapla.dhbw-karlsruhe.de/rapla?page=ical&user=eisenbiegler&file=TINF19B4")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ struct HomeView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(Color.gray)
|
.fill(Color.darkModePrimaryColor)
|
||||||
)
|
)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,8 @@ struct HomeView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
VStack {
|
VStack {
|
||||||
Text("information".localized(tableName: "HomeView", plural: false))
|
Text("information".localized(tableName: "HomeView", plural: false))
|
||||||
.font(.title3)
|
.font(.headline)
|
||||||
|
.fontWeight(.semibold)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
@@ -59,7 +60,7 @@ struct HomeView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(Color.gray)
|
.fill(Color.darkModePrimaryColor)
|
||||||
)
|
)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@@ -228,7 +229,8 @@ struct UpcomingLecturesBlock: View {
|
|||||||
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){
|
||||||
@@ -245,7 +247,7 @@ struct UpcomingLecturesBlock: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(Color.gray)
|
.fill(Color.darkModePrimaryColor)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,11 +276,11 @@ struct UpcomingExamsBlock: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(Color.gray)
|
.fill(Color.darkModePrimaryColor)
|
||||||
)
|
)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.stroke(Color.red, lineWidth: 4)
|
.stroke(Color("AccentColor"), lineWidth: 4)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ struct LecturePlanItem: View {
|
|||||||
.bold()
|
.bold()
|
||||||
Text(event.location!)
|
Text(event.location!)
|
||||||
.bold()
|
.bold()
|
||||||
Text("WIP")
|
Text(!event.lecturerList.isEmpty ? event.lecturerList[0].wrappedName : "")
|
||||||
.bold()
|
.bold()
|
||||||
}.frame(maxWidth: .infinity, alignment: .leading)
|
}.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,6 @@ struct LecturePlanItem: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
event.isHidden = !isHidden
|
event.isHidden = !isHidden
|
||||||
self.isHidden = !isHidden
|
self.isHidden = !isHidden
|
||||||
PersistenceController.shared.save()
|
|
||||||
}){
|
}){
|
||||||
if(self.isHidden){
|
if(self.isHidden){
|
||||||
Text("Show")
|
Text("Show")
|
||||||
@@ -74,6 +73,9 @@ struct LecturePlanItem: View {
|
|||||||
.onAppear{
|
.onAppear{
|
||||||
self.isHidden = event.isHidden
|
self.isHidden = event.isHidden
|
||||||
}
|
}
|
||||||
|
.onDisappear{
|
||||||
|
PersistenceController.shared.save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,39 +7,32 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CoreData
|
import CoreData
|
||||||
|
import Combine
|
||||||
|
|
||||||
struct LecturePlanList: View {
|
struct LecturePlanList: View {
|
||||||
@State private var events: [RaPlaEvent] = []
|
@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.startDate!))
|
|
||||||
.foregroundColor(getEventForegroundColor(for: event))
|
|
||||||
Text(event.summary!)
|
|
||||||
.foregroundColor(getEventForegroundColor(for: event))
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
DayWithEventsBlock(date: key, eventsList: value, parent: self)
|
||||||
if(event.isHidden) {
|
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 = RaPlaEvent.getSpecified(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 = RaPlaEvent.getSpecified(sortDescriptors: sortDescriptors)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,13 +74,22 @@ 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: 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
|
var textColor: Color = .primary
|
||||||
if(event.category! == "Prüfung") {
|
if(event.category! == "Prüfung") {
|
||||||
textColor = Color.red
|
textColor = Color.red
|
||||||
@@ -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(#colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 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.gray)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user