├── .github └── FUNDING.yml ├── GradeCalc.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── marlon.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── GradeCalc ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app-icon-iTunesArtwork.png │ │ ├── app-icon@120x120-1.png │ │ ├── app-icon@120x120.png │ │ ├── app-icon@152x152.png │ │ ├── app-icon@167x167.png │ │ ├── app-icon@180x180.png │ │ ├── app-icon@20x20.png │ │ ├── app-icon@29x29.png │ │ ├── app-icon@40x40-1.png │ │ ├── app-icon@40x40-2.png │ │ ├── app-icon@40x40.png │ │ ├── app-icon@58x58-1.png │ │ ├── app-icon@58x58.png │ │ ├── app-icon@60x60.png │ │ ├── app-icon@76x76.png │ │ ├── app-icon@80x80-1.png │ │ ├── app-icon@80x80.png │ │ └── app-icon@87x87.png │ ├── BlueBackground.colorset │ │ └── Contents.json │ ├── Contents.json │ ├── DarkBlueBackground.colorset │ │ └── Contents.json │ ├── GradientColor1.colorset │ │ └── Contents.json │ ├── GradientColor2.colorset │ │ └── Contents.json │ ├── Menu.imageset │ │ ├── Contents.json │ │ ├── menu_icon.png │ │ ├── menu_icon@2x.png │ │ └── menu_icon@3x.png │ ├── Orange.colorset │ │ └── Contents.json │ ├── Purple.colorset │ │ └── Contents.json │ └── WhiteBackground.colorset │ │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── CoreData │ ├── Semester+CoreDataClass.swift │ ├── Semester+CoreDataProperties.swift │ ├── Subject+CoreDataClass.swift │ └── Subject+CoreDataProperties.swift ├── GradeCalc.xcdatamodeld │ ├── .xccurrentversion │ └── GradeCalc.xcdatamodel │ │ └── contents ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SceneDelegate.swift ├── Views │ ├── Alert │ │ └── TextAlert.swift │ ├── GradeAverageView.swift │ ├── HostingController.swift │ ├── Subject │ │ ├── SubjectAddVIew.swift │ │ ├── SubjectCellView.swift │ │ └── SubjectListView.swift │ └── Utils │ │ └── HideRowSeparatorModifier.swift ├── de.lproj │ └── Localizable.strings └── en.lproj │ └── Localizable.strings ├── Media └── Banner.png ├── README.md └── www └── index.html /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: marlon360 2 | ko_fi: marlon360 3 | -------------------------------------------------------------------------------- /GradeCalc.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3766FCBE27FF29D90044A731 /* HideRowSeparatorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3766FCBD27FF29D90044A731 /* HideRowSeparatorModifier.swift */; }; 11 | 3774F74F2419158B0093C4A1 /* HostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3774F74E2419158B0093C4A1 /* HostingController.swift */; }; 12 | 377D7DC22415959000CD6582 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D7DC12415959000CD6582 /* AppDelegate.swift */; }; 13 | 377D7DC42415959000CD6582 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D7DC32415959000CD6582 /* SceneDelegate.swift */; }; 14 | 377D7DC72415959000CD6582 /* GradeCalc.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 377D7DC52415959000CD6582 /* GradeCalc.xcdatamodeld */; }; 15 | 377D7DC92415959000CD6582 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D7DC82415959000CD6582 /* ContentView.swift */; }; 16 | 377D7DCB2415959200CD6582 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 377D7DCA2415959200CD6582 /* Assets.xcassets */; }; 17 | 377D7DCE2415959200CD6582 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 377D7DCD2415959200CD6582 /* Preview Assets.xcassets */; }; 18 | 377D7DD12415959200CD6582 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 377D7DCF2415959200CD6582 /* LaunchScreen.storyboard */; }; 19 | 377D7DE72415A03E00CD6582 /* SubjectListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D7DE62415A03E00CD6582 /* SubjectListView.swift */; }; 20 | 377D7DE92415A2B400CD6582 /* SubjectAddVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D7DE82415A2B400CD6582 /* SubjectAddVIew.swift */; }; 21 | 377D7DF32415B6BA00CD6582 /* GradeAverageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D7DF22415B6BA00CD6582 /* GradeAverageView.swift */; }; 22 | 377D7DF52415BB3D00CD6582 /* SubjectCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D7DF42415BB3D00CD6582 /* SubjectCellView.swift */; }; 23 | 37868D3D241802C6000AEBD6 /* Subject+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37868D39241802C6000AEBD6 /* Subject+CoreDataClass.swift */; }; 24 | 37868D3E241802C6000AEBD6 /* Subject+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37868D3A241802C6000AEBD6 /* Subject+CoreDataProperties.swift */; }; 25 | 37868D3F241802C6000AEBD6 /* Semester+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37868D3B241802C6000AEBD6 /* Semester+CoreDataClass.swift */; }; 26 | 37868D40241802C6000AEBD6 /* Semester+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37868D3C241802C6000AEBD6 /* Semester+CoreDataProperties.swift */; }; 27 | 37B340FA241D803A004EB721 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 37B340FC241D803A004EB721 /* Localizable.strings */; }; 28 | 37DC2995247A909D00DADF17 /* TextAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DC2994247A909D00DADF17 /* TextAlert.swift */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 3766FCBD27FF29D90044A731 /* HideRowSeparatorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideRowSeparatorModifier.swift; sourceTree = ""; }; 33 | 3774F74E2419158B0093C4A1 /* HostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingController.swift; sourceTree = ""; }; 34 | 377D7DBE2415959000CD6582 /* GradeCalc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GradeCalc.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 377D7DC12415959000CD6582 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | 377D7DC32415959000CD6582 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 37 | 377D7DC62415959000CD6582 /* GradeCalc.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = GradeCalc.xcdatamodel; sourceTree = ""; }; 38 | 377D7DC82415959000CD6582 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 39 | 377D7DCA2415959200CD6582 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | 377D7DCD2415959200CD6582 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 41 | 377D7DD02415959200CD6582 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 42 | 377D7DD22415959200CD6582 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 377D7DE62415A03E00CD6582 /* SubjectListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubjectListView.swift; sourceTree = ""; }; 44 | 377D7DE82415A2B400CD6582 /* SubjectAddVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubjectAddVIew.swift; sourceTree = ""; }; 45 | 377D7DF22415B6BA00CD6582 /* GradeAverageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradeAverageView.swift; sourceTree = ""; }; 46 | 377D7DF42415BB3D00CD6582 /* SubjectCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubjectCellView.swift; sourceTree = ""; }; 47 | 37868D39241802C6000AEBD6 /* Subject+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Subject+CoreDataClass.swift"; path = "GradeCalc/CoreData/Subject+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; 48 | 37868D3A241802C6000AEBD6 /* Subject+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Subject+CoreDataProperties.swift"; path = "GradeCalc/CoreData/Subject+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; 49 | 37868D3B241802C6000AEBD6 /* Semester+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Semester+CoreDataClass.swift"; path = "GradeCalc/CoreData/Semester+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; 50 | 37868D3C241802C6000AEBD6 /* Semester+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Semester+CoreDataProperties.swift"; path = "GradeCalc/CoreData/Semester+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; 51 | 37B340FD241D804C004EB721 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 52 | 37B340FE241D809B004EB721 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 53 | 37DC2994247A909D00DADF17 /* TextAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextAlert.swift; sourceTree = ""; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 377D7DBB2415959000CD6582 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 3766FCBC27FF29BC0044A731 /* Utils */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 3766FCBD27FF29D90044A731 /* HideRowSeparatorModifier.swift */, 71 | ); 72 | path = Utils; 73 | sourceTree = ""; 74 | }; 75 | 377D7DB52415959000CD6582 = { 76 | isa = PBXGroup; 77 | children = ( 78 | 377D7DC02415959000CD6582 /* GradeCalc */, 79 | 377D7DBF2415959000CD6582 /* Products */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | 377D7DBF2415959000CD6582 /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 377D7DBE2415959000CD6582 /* GradeCalc.app */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 377D7DC02415959000CD6582 /* GradeCalc */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 377D7DE52415A02000CD6582 /* Views */, 95 | 377D7DD824159FF000CD6582 /* CoreData */, 96 | 377D7DC12415959000CD6582 /* AppDelegate.swift */, 97 | 377D7DC32415959000CD6582 /* SceneDelegate.swift */, 98 | 377D7DC82415959000CD6582 /* ContentView.swift */, 99 | 377D7DCA2415959200CD6582 /* Assets.xcassets */, 100 | 377D7DCF2415959200CD6582 /* LaunchScreen.storyboard */, 101 | 377D7DD22415959200CD6582 /* Info.plist */, 102 | 377D7DC52415959000CD6582 /* GradeCalc.xcdatamodeld */, 103 | 377D7DCC2415959200CD6582 /* Preview Content */, 104 | 37B340FC241D803A004EB721 /* Localizable.strings */, 105 | ); 106 | path = GradeCalc; 107 | sourceTree = ""; 108 | }; 109 | 377D7DCC2415959200CD6582 /* Preview Content */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 377D7DCD2415959200CD6582 /* Preview Assets.xcassets */, 113 | ); 114 | path = "Preview Content"; 115 | sourceTree = ""; 116 | }; 117 | 377D7DD824159FF000CD6582 /* CoreData */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 37868D39241802C6000AEBD6 /* Subject+CoreDataClass.swift */, 121 | 37868D3A241802C6000AEBD6 /* Subject+CoreDataProperties.swift */, 122 | 37868D3B241802C6000AEBD6 /* Semester+CoreDataClass.swift */, 123 | 37868D3C241802C6000AEBD6 /* Semester+CoreDataProperties.swift */, 124 | ); 125 | path = CoreData; 126 | sourceTree = ""; 127 | }; 128 | 377D7DE52415A02000CD6582 /* Views */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 3766FCBC27FF29BC0044A731 /* Utils */, 132 | 37DC2993247A908700DADF17 /* Alert */, 133 | 377D7DFA2416715300CD6582 /* Subject */, 134 | 377D7DF22415B6BA00CD6582 /* GradeAverageView.swift */, 135 | 3774F74E2419158B0093C4A1 /* HostingController.swift */, 136 | ); 137 | path = Views; 138 | sourceTree = ""; 139 | }; 140 | 377D7DFA2416715300CD6582 /* Subject */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 377D7DE62415A03E00CD6582 /* SubjectListView.swift */, 144 | 377D7DE82415A2B400CD6582 /* SubjectAddVIew.swift */, 145 | 377D7DF42415BB3D00CD6582 /* SubjectCellView.swift */, 146 | ); 147 | path = Subject; 148 | sourceTree = ""; 149 | }; 150 | 37DC2993247A908700DADF17 /* Alert */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 37DC2994247A909D00DADF17 /* TextAlert.swift */, 154 | ); 155 | path = Alert; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXNativeTarget section */ 161 | 377D7DBD2415959000CD6582 /* GradeCalc */ = { 162 | isa = PBXNativeTarget; 163 | buildConfigurationList = 377D7DD52415959200CD6582 /* Build configuration list for PBXNativeTarget "GradeCalc" */; 164 | buildPhases = ( 165 | 377D7DBA2415959000CD6582 /* Sources */, 166 | 377D7DBB2415959000CD6582 /* Frameworks */, 167 | 377D7DBC2415959000CD6582 /* Resources */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | ); 173 | name = GradeCalc; 174 | productName = GradeCalc; 175 | productReference = 377D7DBE2415959000CD6582 /* GradeCalc.app */; 176 | productType = "com.apple.product-type.application"; 177 | }; 178 | /* End PBXNativeTarget section */ 179 | 180 | /* Begin PBXProject section */ 181 | 377D7DB62415959000CD6582 /* Project object */ = { 182 | isa = PBXProject; 183 | attributes = { 184 | LastSwiftUpdateCheck = 1130; 185 | LastUpgradeCheck = 1330; 186 | ORGANIZATIONNAME = "Marlon Lückert"; 187 | TargetAttributes = { 188 | 377D7DBD2415959000CD6582 = { 189 | CreatedOnToolsVersion = 11.3.1; 190 | }; 191 | }; 192 | }; 193 | buildConfigurationList = 377D7DB92415959000CD6582 /* Build configuration list for PBXProject "GradeCalc" */; 194 | compatibilityVersion = "Xcode 9.3"; 195 | developmentRegion = en; 196 | hasScannedForEncodings = 0; 197 | knownRegions = ( 198 | en, 199 | Base, 200 | de, 201 | ); 202 | mainGroup = 377D7DB52415959000CD6582; 203 | productRefGroup = 377D7DBF2415959000CD6582 /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | 377D7DBD2415959000CD6582 /* GradeCalc */, 208 | ); 209 | }; 210 | /* End PBXProject section */ 211 | 212 | /* Begin PBXResourcesBuildPhase section */ 213 | 377D7DBC2415959000CD6582 /* Resources */ = { 214 | isa = PBXResourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | 377D7DD12415959200CD6582 /* LaunchScreen.storyboard in Resources */, 218 | 37B340FA241D803A004EB721 /* Localizable.strings in Resources */, 219 | 377D7DCE2415959200CD6582 /* Preview Assets.xcassets in Resources */, 220 | 377D7DCB2415959200CD6582 /* Assets.xcassets in Resources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXResourcesBuildPhase section */ 225 | 226 | /* Begin PBXSourcesBuildPhase section */ 227 | 377D7DBA2415959000CD6582 /* Sources */ = { 228 | isa = PBXSourcesBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | 37868D3F241802C6000AEBD6 /* Semester+CoreDataClass.swift in Sources */, 232 | 37868D3D241802C6000AEBD6 /* Subject+CoreDataClass.swift in Sources */, 233 | 377D7DE92415A2B400CD6582 /* SubjectAddVIew.swift in Sources */, 234 | 37DC2995247A909D00DADF17 /* TextAlert.swift in Sources */, 235 | 377D7DC22415959000CD6582 /* AppDelegate.swift in Sources */, 236 | 3774F74F2419158B0093C4A1 /* HostingController.swift in Sources */, 237 | 377D7DC72415959000CD6582 /* GradeCalc.xcdatamodeld in Sources */, 238 | 3766FCBE27FF29D90044A731 /* HideRowSeparatorModifier.swift in Sources */, 239 | 377D7DC92415959000CD6582 /* ContentView.swift in Sources */, 240 | 377D7DF52415BB3D00CD6582 /* SubjectCellView.swift in Sources */, 241 | 377D7DF32415B6BA00CD6582 /* GradeAverageView.swift in Sources */, 242 | 377D7DE72415A03E00CD6582 /* SubjectListView.swift in Sources */, 243 | 37868D40241802C6000AEBD6 /* Semester+CoreDataProperties.swift in Sources */, 244 | 37868D3E241802C6000AEBD6 /* Subject+CoreDataProperties.swift in Sources */, 245 | 377D7DC42415959000CD6582 /* SceneDelegate.swift in Sources */, 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | /* End PBXSourcesBuildPhase section */ 250 | 251 | /* Begin PBXVariantGroup section */ 252 | 377D7DCF2415959200CD6582 /* LaunchScreen.storyboard */ = { 253 | isa = PBXVariantGroup; 254 | children = ( 255 | 377D7DD02415959200CD6582 /* Base */, 256 | ); 257 | name = LaunchScreen.storyboard; 258 | sourceTree = ""; 259 | }; 260 | 37B340FC241D803A004EB721 /* Localizable.strings */ = { 261 | isa = PBXVariantGroup; 262 | children = ( 263 | 37B340FD241D804C004EB721 /* en */, 264 | 37B340FE241D809B004EB721 /* de */, 265 | ); 266 | name = Localizable.strings; 267 | sourceTree = ""; 268 | }; 269 | /* End PBXVariantGroup section */ 270 | 271 | /* Begin XCBuildConfiguration section */ 272 | 377D7DD32415959200CD6582 /* Debug */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | ALWAYS_SEARCH_USER_PATHS = NO; 276 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 277 | CLANG_ANALYZER_NONNULL = YES; 278 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 280 | CLANG_CXX_LIBRARY = "libc++"; 281 | CLANG_ENABLE_MODULES = YES; 282 | CLANG_ENABLE_OBJC_ARC = YES; 283 | CLANG_ENABLE_OBJC_WEAK = YES; 284 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_COMMA = YES; 287 | CLANG_WARN_CONSTANT_CONVERSION = YES; 288 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 290 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INFINITE_RECURSION = YES; 294 | CLANG_WARN_INT_CONVERSION = YES; 295 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 297 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 299 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 300 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 301 | CLANG_WARN_STRICT_PROTOTYPES = YES; 302 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 303 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 304 | CLANG_WARN_UNREACHABLE_CODE = YES; 305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 306 | COPY_PHASE_STRIP = NO; 307 | DEBUG_INFORMATION_FORMAT = dwarf; 308 | ENABLE_STRICT_OBJC_MSGSEND = YES; 309 | ENABLE_TESTABILITY = YES; 310 | GCC_C_LANGUAGE_STANDARD = gnu11; 311 | GCC_DYNAMIC_NO_PIC = NO; 312 | GCC_NO_COMMON_BLOCKS = YES; 313 | GCC_OPTIMIZATION_LEVEL = 0; 314 | GCC_PREPROCESSOR_DEFINITIONS = ( 315 | "DEBUG=1", 316 | "$(inherited)", 317 | ); 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 325 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 326 | MTL_FAST_MATH = YES; 327 | ONLY_ACTIVE_ARCH = YES; 328 | SDKROOT = iphoneos; 329 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 330 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 331 | }; 332 | name = Debug; 333 | }; 334 | 377D7DD42415959200CD6582 /* Release */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 339 | CLANG_ANALYZER_NONNULL = YES; 340 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 341 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 342 | CLANG_CXX_LIBRARY = "libc++"; 343 | CLANG_ENABLE_MODULES = YES; 344 | CLANG_ENABLE_OBJC_ARC = YES; 345 | CLANG_ENABLE_OBJC_WEAK = YES; 346 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 347 | CLANG_WARN_BOOL_CONVERSION = YES; 348 | CLANG_WARN_COMMA = YES; 349 | CLANG_WARN_CONSTANT_CONVERSION = YES; 350 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 353 | CLANG_WARN_EMPTY_BODY = YES; 354 | CLANG_WARN_ENUM_CONVERSION = YES; 355 | CLANG_WARN_INFINITE_RECURSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 361 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 363 | CLANG_WARN_STRICT_PROTOTYPES = YES; 364 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 365 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | COPY_PHASE_STRIP = NO; 369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 370 | ENABLE_NS_ASSERTIONS = NO; 371 | ENABLE_STRICT_OBJC_MSGSEND = YES; 372 | GCC_C_LANGUAGE_STANDARD = gnu11; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 376 | GCC_WARN_UNDECLARED_SELECTOR = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 378 | GCC_WARN_UNUSED_FUNCTION = YES; 379 | GCC_WARN_UNUSED_VARIABLE = YES; 380 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 381 | MTL_ENABLE_DEBUG_INFO = NO; 382 | MTL_FAST_MATH = YES; 383 | SDKROOT = iphoneos; 384 | SWIFT_COMPILATION_MODE = wholemodule; 385 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 386 | VALIDATE_PRODUCT = YES; 387 | }; 388 | name = Release; 389 | }; 390 | 377D7DD62415959200CD6582 /* Debug */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 394 | CODE_SIGN_STYLE = Automatic; 395 | CURRENT_PROJECT_VERSION = 2; 396 | DEVELOPMENT_ASSET_PATHS = "\"GradeCalc/Preview Content\""; 397 | DEVELOPMENT_TEAM = 743RFNFSA2; 398 | ENABLE_PREVIEWS = YES; 399 | INFOPLIST_FILE = GradeCalc/Info.plist; 400 | LD_RUNPATH_SEARCH_PATHS = ( 401 | "$(inherited)", 402 | "@executable_path/Frameworks", 403 | ); 404 | MARKETING_VERSION = 1.1; 405 | PRODUCT_BUNDLE_IDENTIFIER = de.marlon.GradeCalc; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SWIFT_VERSION = 5.0; 408 | TARGETED_DEVICE_FAMILY = "1,2"; 409 | }; 410 | name = Debug; 411 | }; 412 | 377D7DD72415959200CD6582 /* Release */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | CODE_SIGN_STYLE = Automatic; 417 | CURRENT_PROJECT_VERSION = 2; 418 | DEVELOPMENT_ASSET_PATHS = "\"GradeCalc/Preview Content\""; 419 | DEVELOPMENT_TEAM = 743RFNFSA2; 420 | ENABLE_PREVIEWS = YES; 421 | INFOPLIST_FILE = GradeCalc/Info.plist; 422 | LD_RUNPATH_SEARCH_PATHS = ( 423 | "$(inherited)", 424 | "@executable_path/Frameworks", 425 | ); 426 | MARKETING_VERSION = 1.1; 427 | PRODUCT_BUNDLE_IDENTIFIER = de.marlon.GradeCalc; 428 | PRODUCT_NAME = "$(TARGET_NAME)"; 429 | SWIFT_VERSION = 5.0; 430 | TARGETED_DEVICE_FAMILY = "1,2"; 431 | }; 432 | name = Release; 433 | }; 434 | /* End XCBuildConfiguration section */ 435 | 436 | /* Begin XCConfigurationList section */ 437 | 377D7DB92415959000CD6582 /* Build configuration list for PBXProject "GradeCalc" */ = { 438 | isa = XCConfigurationList; 439 | buildConfigurations = ( 440 | 377D7DD32415959200CD6582 /* Debug */, 441 | 377D7DD42415959200CD6582 /* Release */, 442 | ); 443 | defaultConfigurationIsVisible = 0; 444 | defaultConfigurationName = Release; 445 | }; 446 | 377D7DD52415959200CD6582 /* Build configuration list for PBXNativeTarget "GradeCalc" */ = { 447 | isa = XCConfigurationList; 448 | buildConfigurations = ( 449 | 377D7DD62415959200CD6582 /* Debug */, 450 | 377D7DD72415959200CD6582 /* Release */, 451 | ); 452 | defaultConfigurationIsVisible = 0; 453 | defaultConfigurationName = Release; 454 | }; 455 | /* End XCConfigurationList section */ 456 | 457 | /* Begin XCVersionGroup section */ 458 | 377D7DC52415959000CD6582 /* GradeCalc.xcdatamodeld */ = { 459 | isa = XCVersionGroup; 460 | children = ( 461 | 377D7DC62415959000CD6582 /* GradeCalc.xcdatamodel */, 462 | ); 463 | currentVersion = 377D7DC62415959000CD6582 /* GradeCalc.xcdatamodel */; 464 | path = GradeCalc.xcdatamodeld; 465 | sourceTree = ""; 466 | versionGroupType = wrapper.xcdatamodel; 467 | }; 468 | /* End XCVersionGroup section */ 469 | }; 470 | rootObject = 377D7DB62415959000CD6582 /* Project object */; 471 | } 472 | -------------------------------------------------------------------------------- /GradeCalc.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GradeCalc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GradeCalc.xcodeproj/xcuserdata/marlon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /GradeCalc.xcodeproj/xcuserdata/marlon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GradeCalc.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /GradeCalc/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 08.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | // MARK: UISceneSession Lifecycle 23 | 24 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 25 | // Called when a new scene session is being created. 26 | // Use this method to select a configuration to create the new scene with. 27 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 28 | } 29 | 30 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 31 | // Called when the user discards a scene session. 32 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 33 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 34 | } 35 | 36 | // MARK: - Core Data stack 37 | 38 | lazy var persistentContainer: NSPersistentContainer = { 39 | /* 40 | The persistent container for the application. This implementation 41 | creates and returns a container, having loaded the store for the 42 | application to it. This property is optional since there are legitimate 43 | error conditions that could cause the creation of the store to fail. 44 | */ 45 | let container = NSPersistentContainer(name: "GradeCalc") 46 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 47 | if let error = error as NSError? { 48 | // Replace this implementation with code to handle the error appropriately. 49 | // 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. 50 | 51 | /* 52 | Typical reasons for an error here include: 53 | * The parent directory does not exist, cannot be created, or disallows writing. 54 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 55 | * The device is out of space. 56 | * The store could not be migrated to the current model version. 57 | Check the error message to determine what the actual problem was. 58 | */ 59 | fatalError("Unresolved error \(error), \(error.userInfo)") 60 | } 61 | }) 62 | return container 63 | }() 64 | 65 | // MARK: - Core Data Saving support 66 | 67 | func saveContext () { 68 | let context = persistentContainer.viewContext 69 | if context.hasChanges { 70 | do { 71 | try context.save() 72 | } catch { 73 | // Replace this implementation with code to handle the error appropriately. 74 | // 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. 75 | let nserror = error as NSError 76 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "app-icon@40x40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "app-icon@60x60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "app-icon@58x58-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "app-icon@87x87.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "app-icon@80x80-1.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "app-icon@120x120-1.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "app-icon@120x120.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "app-icon@180x180.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "app-icon@20x20.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "app-icon@40x40-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "app-icon@29x29.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "app-icon@58x58.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "app-icon@40x40-2.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "app-icon@80x80.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "app-icon@76x76.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "app-icon@152x152.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "app-icon@167x167.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "app-icon-iTunesArtwork.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon-iTunesArtwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon-iTunesArtwork.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@120x120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@120x120-1.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@120x120.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@152x152.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@167x167.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@180x180.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@20x20.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@29x29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@29x29.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@40x40-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@40x40-1.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@40x40-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@40x40-2.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@40x40.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@58x58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@58x58-1.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@58x58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@58x58.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@60x60.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@76x76.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@80x80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@80x80-1.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@80x80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@80x80.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@87x87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/AppIcon.appiconset/app-icon@87x87.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/BlueBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF7", 9 | "green" : "0xEF", 10 | "red" : "0xEA" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.000", 27 | "green" : "0.000", 28 | "red" : "0.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/DarkBlueBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.149", 13 | "alpha" : "1.000", 14 | "blue" : "0.235", 15 | "green" : "0.161" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/GradientColor1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x09", 13 | "alpha" : "1.000", 14 | "blue" : "0xF6", 15 | "green" : "0x9F" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0x06", 31 | "alpha" : "1.000", 32 | "blue" : "0x99", 33 | "green" : "0x63" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/GradientColor2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xB0", 13 | "alpha" : "1.000", 14 | "blue" : "0xFF", 15 | "green" : "0x39" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0x69", 31 | "alpha" : "1.000", 32 | "blue" : "0x99", 33 | "green" : "0x22" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/Menu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "menu_icon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "menu_icon@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "menu_icon@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/Menu.imageset/menu_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/Menu.imageset/menu_icon.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/Menu.imageset/menu_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/Menu.imageset/menu_icon@2x.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/Menu.imageset/menu_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/GradeCalc/Assets.xcassets/Menu.imageset/menu_icon@3x.png -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/Orange.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xFF", 13 | "alpha" : "1.000", 14 | "blue" : "0x00", 15 | "green" : "0x95" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0xFF", 31 | "alpha" : "1.000", 32 | "blue" : "0x4B", 33 | "green" : "0x4B" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/Purple.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x8C", 13 | "alpha" : "1.000", 14 | "blue" : "0xFD", 15 | "green" : "0x4F" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /GradeCalc/Assets.xcassets/WhiteBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "1.000", 13 | "alpha" : "1.000", 14 | "blue" : "1.000", 15 | "green" : "1.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0x1A", 31 | "alpha" : "1.000", 32 | "blue" : "0x1A", 33 | "green" : "0x1A" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /GradeCalc/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GradeCalc/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 08.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | var body: some View { 13 | SubjectListView() 14 | } 15 | } 16 | 17 | struct ContentView_Previews: PreviewProvider { 18 | static var previews: some View { 19 | ContentView() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /GradeCalc/CoreData/Semester+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Semester+CoreDataClass.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 10.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | 14 | public class Semester: NSManagedObject, Identifiable { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /GradeCalc/CoreData/Semester+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Semester+CoreDataProperties.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 10.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | 14 | extension Semester { 15 | 16 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 17 | return NSFetchRequest(entityName: "Semester") 18 | } 19 | 20 | @NSManaged public var title: String? 21 | @NSManaged public var subjects: NSSet? 22 | 23 | public var subjectsArray: [Subject] { 24 | let set = subjects as? Set ?? [] 25 | return set.sorted { 26 | $0.title! < $1.title! 27 | } 28 | } 29 | 30 | } 31 | 32 | // MARK: Generated accessors for subjects 33 | extension Semester { 34 | 35 | @objc(addSubjectsObject:) 36 | @NSManaged public func addToSubjects(_ value: Subject) 37 | 38 | @objc(removeSubjectsObject:) 39 | @NSManaged public func removeFromSubjects(_ value: Subject) 40 | 41 | @objc(addSubjects:) 42 | @NSManaged public func addToSubjects(_ values: NSSet) 43 | 44 | @objc(removeSubjects:) 45 | @NSManaged public func removeFromSubjects(_ values: NSSet) 46 | 47 | } 48 | -------------------------------------------------------------------------------- /GradeCalc/CoreData/Subject+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Subject+CoreDataClass.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 10.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | 14 | public class Subject: NSManagedObject, Identifiable { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /GradeCalc/CoreData/Subject+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Subject+CoreDataProperties.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 10.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | 14 | extension Subject { 15 | 16 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 17 | return NSFetchRequest(entityName: "Subject") 18 | } 19 | 20 | @NSManaged public var title: String? 21 | @NSManaged public var weight: Float 22 | @NSManaged public var grade: Float 23 | @NSManaged public var active: Bool 24 | @NSManaged public var simulation: Bool 25 | @NSManaged public var simMin: Float 26 | @NSManaged public var simMax: Float 27 | @NSManaged public var semester: Semester? 28 | 29 | } 30 | -------------------------------------------------------------------------------- /GradeCalc/GradeCalc.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | GradeCalc.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /GradeCalc/GradeCalc.xcdatamodeld/GradeCalc.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /GradeCalc/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /GradeCalc/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GradeCalc/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 08.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | import CoreData 12 | 13 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 19 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 20 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 21 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 22 | 23 | // Get the managed object context from the shared persistent container. 24 | let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 25 | 26 | context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 27 | 28 | // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath. 29 | // Add `@Environment(\.managedObjectContext)` in the views that will need the context. 30 | let contentView = ContentView().environment(\.managedObjectContext, context) 31 | 32 | // Use a UIHostingController as window root view controller. 33 | if let windowScene = scene as? UIWindowScene { 34 | let window = UIWindow(windowScene: windowScene) 35 | window.rootViewController = HostingController(rootView: AnyView(contentView)) 36 | self.window = window 37 | window.makeKeyAndVisible() 38 | } 39 | } 40 | 41 | func sceneDidDisconnect(_ scene: UIScene) { 42 | // Called as the scene is being released by the system. 43 | // This occurs shortly after the scene enters the background, or when its session is discarded. 44 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 45 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 46 | } 47 | 48 | func sceneDidBecomeActive(_ scene: UIScene) { 49 | // Called when the scene has moved from an inactive state to an active state. 50 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 51 | } 52 | 53 | func sceneWillResignActive(_ scene: UIScene) { 54 | // Called when the scene will move from an active state to an inactive state. 55 | // This may occur due to temporary interruptions (ex. an incoming phone call). 56 | } 57 | 58 | func sceneWillEnterForeground(_ scene: UIScene) { 59 | // Called as the scene transitions from the background to the foreground. 60 | // Use this method to undo the changes made on entering the background. 61 | } 62 | 63 | func sceneDidEnterBackground(_ scene: UIScene) { 64 | // Called as the scene transitions from the foreground to the background. 65 | // Use this method to save data, release shared resources, and store enough scene-specific state information 66 | // to restore the scene back to its current state. 67 | 68 | // Save changes in the application's managed object context when the application transitions to the background. 69 | (UIApplication.shared.delegate as? AppDelegate)?.saveContext() 70 | } 71 | 72 | 73 | } 74 | 75 | -------------------------------------------------------------------------------- /GradeCalc/Views/Alert/TextAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextAlert.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 24.05.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UIKit 11 | 12 | extension UIAlertController { 13 | convenience init(alert: TextAlert) { 14 | self.init(title: alert.title, message: nil, preferredStyle: .alert) 15 | addTextField { $0.placeholder = alert.placeholder } 16 | addAction(UIAlertAction(title: alert.cancel, style: .cancel) { _ in 17 | alert.action(nil) 18 | }) 19 | let textField = self.textFields?.first 20 | addAction(UIAlertAction(title: alert.accept, style: .default) { _ in 21 | alert.action(textField?.text) 22 | }) 23 | } 24 | } 25 | 26 | 27 | 28 | struct AlertWrapper: UIViewControllerRepresentable { 29 | @Binding var isPresented: Bool 30 | let alert: TextAlert 31 | let content: Content 32 | 33 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIHostingController { 34 | UIHostingController(rootView: content) 35 | } 36 | 37 | final class Coordinator { 38 | var alertController: UIAlertController? 39 | init(_ controller: UIAlertController? = nil) { 40 | self.alertController = controller 41 | } 42 | } 43 | 44 | func makeCoordinator() -> Coordinator { 45 | return Coordinator() 46 | } 47 | 48 | 49 | func updateUIViewController(_ uiViewController: UIHostingController, context: UIViewControllerRepresentableContext) { 50 | uiViewController.rootView = content 51 | if isPresented && uiViewController.presentedViewController == nil { 52 | var alert = self.alert 53 | alert.action = { 54 | self.isPresented = false 55 | self.alert.action($0) 56 | } 57 | context.coordinator.alertController = UIAlertController(alert: alert) 58 | uiViewController.present(context.coordinator.alertController!, animated: true) 59 | } 60 | if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController { 61 | uiViewController.dismiss(animated: true) 62 | } 63 | } 64 | } 65 | 66 | public struct TextAlert { 67 | public var title: String 68 | public var placeholder: String = "" 69 | public var accept: String = "OK" 70 | public var cancel: String = "Cancel" 71 | public var action: (String?) -> () 72 | } 73 | 74 | extension View { 75 | public func alert(isPresented: Binding, _ alert: TextAlert) -> some View { 76 | AlertWrapper(isPresented: isPresented, alert: alert, content: self) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /GradeCalc/Views/GradeAverageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GradeAverageView.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 09.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct GradeAverageView: View { 13 | 14 | @Environment(\.managedObjectContext) var managedObjectContext 15 | 16 | @FetchRequest(entity: Semester.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Semester.title, ascending: true)]) var semesters: FetchedResults 17 | 18 | @Binding var menuOpen: Bool 19 | @Binding var simulation: Bool 20 | 21 | var body: some View { 22 | let average = getAverage(semester: semesters) 23 | let simAverage = getSimulatedAverage(semester: semesters) 24 | 25 | let averageString: LocalizedStringKey = "current average" 26 | let simAverageString: LocalizedStringKey = "simulated average" 27 | 28 | return ( 29 | ZStack { 30 | Rectangle() 31 | .foregroundColor(.clear) 32 | .background(LinearGradient(gradient: Gradient(colors: [Color(UIColor(named: "GradientColor1") ?? .blue), Color(UIColor(named: "GradientColor2") ?? .purple)]), startPoint: .topLeading, endPoint: .bottomTrailing)) 33 | .edgesIgnoringSafeArea(.all) 34 | TabView { 35 | VStack { 36 | Text(average > 0.0 ? String(format: "%.2f", average) : "0") 37 | .font(.system(size: 52)) 38 | .foregroundColor(.white) 39 | .bold() 40 | Text(averageString) 41 | .font(.system(size: 16)) 42 | .foregroundColor(.white) 43 | .padding(.bottom, 6) 44 | } 45 | .padding(.bottom, 20) 46 | VStack { 47 | Text(simAverage.0 > 0.0 ? String(format: "%.2f - %.2f", simAverage.0, simAverage.1) : "0") 48 | .font(.system(size: 52)) 49 | .foregroundColor(.white) 50 | .bold() 51 | 52 | Text(simAverageString) 53 | .font(.system(size: 16)) 54 | .foregroundColor(.white) 55 | .padding(.bottom, 6) 56 | } 57 | .padding(.bottom, 20) 58 | } 59 | .tabViewStyle(.page(indexDisplayMode: .always)) 60 | .padding(.top, -20) 61 | .padding(.bottom, 30) 62 | } 63 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 184, alignment: .trailing) 64 | ) 65 | } 66 | 67 | 68 | func getAverage(semester: FetchedResults) -> Float { 69 | var sum = Float(0) 70 | var count: Float = 0.0 71 | for semester in semesters { 72 | for subject in semester.subjectsArray { 73 | if subject.active && !subject.simulation { 74 | sum += subject.grade * subject.weight 75 | count += subject.weight 76 | } 77 | } 78 | } 79 | if (count > 0.0) { 80 | return sum / count 81 | } 82 | return 0.0 83 | } 84 | 85 | func getSimulatedAverage(semester: FetchedResults) -> (Float, Float) { 86 | var sumMin = Float(0) 87 | var sumMax = Float(0) 88 | var count: Float = 0.0 89 | for semester in semesters { 90 | for subject in semester.subjectsArray { 91 | if subject.active { 92 | if (subject.simulation) { 93 | sumMin += subject.simMin * subject.weight 94 | sumMax += subject.simMax * subject.weight 95 | } else { 96 | sumMin += subject.grade * subject.weight 97 | sumMax += subject.grade * subject.weight 98 | } 99 | count += subject.weight 100 | } 101 | } 102 | } 103 | if (count > 0.0) { 104 | return (sumMin / count, sumMax / count) 105 | } 106 | return (0.0, 0.0) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /GradeCalc/Views/HostingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostingController.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 11.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | class HostingController: UIHostingController { 13 | override var preferredStatusBarStyle: UIStatusBarStyle { 14 | return .lightContent 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /GradeCalc/Views/Subject/SubjectAddVIew.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SemesterAddVIew.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 08.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | final class KeyboardResponder: ObservableObject { 13 | private var notificationCenter: NotificationCenter 14 | @Published private(set) var currentHeight: CGFloat = 0 15 | 16 | init(center: NotificationCenter = .default) { 17 | notificationCenter = center 18 | notificationCenter.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) 19 | notificationCenter.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) 20 | } 21 | 22 | deinit { 23 | notificationCenter.removeObserver(self) 24 | } 25 | 26 | @objc func keyBoardWillShow(notification: Notification) { 27 | if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { 28 | currentHeight = keyboardSize.height 29 | } 30 | } 31 | 32 | @objc func keyBoardWillHide(notification: Notification) { 33 | currentHeight = 0 34 | } 35 | } 36 | 37 | struct SubjectAddView: View { 38 | 39 | @State var subject: Subject? 40 | 41 | @State var title: String 42 | @State private var grade: String 43 | @State private var active: Bool 44 | @State private var simulation: Bool 45 | @State private var simMin: String 46 | @State private var simMax: String 47 | @State private var weight: String 48 | 49 | @State private var selectedSemester: Int 50 | @State private var gradeCounts: Bool 51 | 52 | @State private var isShowingNewSemester = false 53 | @State private var newSemester = "" 54 | 55 | @Binding var isPresented: Bool 56 | 57 | @Environment(\.managedObjectContext) var managedObjectContext 58 | 59 | @FetchRequest(entity: Semester.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Semester.title, ascending: true)]) var semesters: FetchedResults 60 | 61 | @State private var semesterChooser = 0 62 | @State private var simulationChooser: Int 63 | 64 | @ObservedObject private var keyboard = KeyboardResponder() 65 | 66 | init(subject bindedSubject: Subject?, isPresented: Binding) { 67 | self._subject = State(initialValue: bindedSubject) 68 | self._isPresented = isPresented 69 | 70 | if let subject = bindedSubject { 71 | self._title = State(initialValue: subject.title ?? "") 72 | self._grade = State(initialValue: String(subject.grade)) 73 | self._active = State(initialValue: subject.active) 74 | self._simulation = State(initialValue: subject.simulation) 75 | self._simulationChooser = State(initialValue: subject.simulation ? 1 : 0) 76 | self._simMin = State(initialValue: String(subject.simMin)) 77 | self._simMax = State(initialValue: String(subject.simMax)) 78 | self._weight = State(initialValue: String(subject.weight)) 79 | self._selectedSemester = State(initialValue: 0) 80 | self._gradeCounts = State(initialValue: subject.weight != 0.0) 81 | } else { 82 | self._title = State(initialValue: "") 83 | self._grade = State(initialValue: "1.0") 84 | self._active = State(initialValue:true) 85 | self._simulation = State(initialValue:false) 86 | self._simulationChooser = State(initialValue: 0) 87 | self._simMin = State(initialValue:"1.0") 88 | self._simMax = State(initialValue:"4.0") 89 | self._weight = State(initialValue:"1.0") 90 | self._selectedSemester = State(initialValue: 0) 91 | self._gradeCounts = State(initialValue: true) 92 | } 93 | } 94 | 95 | var body: some View { 96 | 97 | let selectedSemesterPicker = Binding(get: { 98 | return self.selectedSemester 99 | }, set: { 100 | self.selectedSemester = $0 101 | if let subject = self.subject { 102 | subject.semester = self.semesters[self.selectedSemester] 103 | } 104 | }) 105 | 106 | return NavigationView { 107 | Form { 108 | 109 | Section() { 110 | TextField("title", text: self.$title) 111 | } 112 | 113 | Section(header: Text("grade")) { 114 | Picker(selection: $simulationChooser, label: Text("semester")) { 115 | Text("final grade").tag(0) 116 | Text("simulated grade").tag(1) 117 | }.pickerStyle(SegmentedPickerStyle()) 118 | if (simulationChooser == 0) { 119 | HStack { 120 | Text("grade:") 121 | TextField("grade", text: self.$grade) 122 | .keyboardType(.decimalPad) 123 | 124 | } 125 | } else { 126 | VStack(spacing: 0) { 127 | HStack { 128 | Text("best grade:") 129 | TextField("grade", text: self.$simMin) 130 | .keyboardType(.decimalPad) 131 | } 132 | Divider() 133 | .offset(x: 0, y: 10) 134 | } 135 | HStack { 136 | Text("worst grade:") 137 | TextField("grade", text: self.$simMax) 138 | .keyboardType(.decimalPad) 139 | } 140 | } 141 | 142 | } 143 | 144 | Section(header: Text("weight")) { 145 | VStack(spacing: 0) { 146 | Toggle(isOn: self.$gradeCounts) { 147 | Text("grade counts") 148 | } 149 | Divider() 150 | .offset(x: 0, y: 10) 151 | 152 | } 153 | HStack { 154 | Text("weight:") 155 | if (self.gradeCounts) { 156 | TextField("weight", text: self.$weight) 157 | .keyboardType(.decimalPad) 158 | } else { 159 | Text("0") 160 | } 161 | } 162 | .disabled(!self.gradeCounts) 163 | .foregroundColor(self.gradeCounts ? .primary : Color.gray) 164 | } 165 | 166 | Section(header: Text("semester")) { 167 | Picker(selection: $semesterChooser, label: Text("semester")) { 168 | Text("choose semester").tag(0) 169 | Text("create semester").tag(1) 170 | }.pickerStyle(SegmentedPickerStyle()) 171 | .onAppear { 172 | if (self.semesters.count == 0) { 173 | self.semesterChooser = 1 174 | } 175 | } 176 | 177 | // choose semester is selected in segemebted control 178 | if (semesterChooser == 0) { 179 | if (semesters.count > 0) { 180 | Picker(selection: selectedSemesterPicker, label: Text("semester")) { 181 | ForEach(0 ..< semesters.count) { 182 | Text(self.semesters[$0].title ?? "semester").tag($0) 183 | } 184 | } 185 | .onAppear() { 186 | if let subject = self.subject { 187 | self.selectedSemester = self.semesters.lastIndex(of: subject.semester!) ?? self.selectedSemester 188 | } 189 | } 190 | } else { 191 | Text("no semester") 192 | } 193 | } 194 | 195 | // create semester is selected in segmented control 196 | if (semesterChooser == 1) { 197 | TextField("semester", text: self.$newSemester) 198 | } 199 | 200 | } 201 | 202 | Section() { 203 | Button(action: { 204 | self.saveSubject() 205 | self.isPresented = false 206 | }) { 207 | Text("save") 208 | .frame(maxWidth: .infinity, alignment: .center) 209 | } 210 | .disabled(!self.isSavable()) 211 | } 212 | 213 | } 214 | .navigationBarTitle(Text(self.getNavigationTitle()), displayMode: .inline) 215 | .navigationBarItems(leading: 216 | Button(action: { 217 | self.isPresented = false 218 | }) { 219 | Text("cancel") 220 | }, trailing: 221 | Button(action: { 222 | self.saveSubject() 223 | self.isPresented = false 224 | }) { 225 | Text("save") 226 | }.disabled(!self.isSavable()) 227 | ) 228 | .padding(.bottom, keyboard.currentHeight) 229 | .edgesIgnoringSafeArea(.bottom) 230 | .animation(.easeOut(duration: 0.16)) 231 | } 232 | } 233 | 234 | func getNavigationTitle() -> LocalizedStringKey { 235 | if (self.subject != nil) { 236 | return "edit subject" 237 | } else { 238 | return "new subject" 239 | } 240 | } 241 | 242 | func isSavable() -> Bool { 243 | 244 | if (self.title == "") { 245 | return false 246 | } 247 | if (self.grade == "") { 248 | return false 249 | } 250 | if (self.weight == "") { 251 | return false 252 | } 253 | 254 | if (self.semesterChooser == 1) { 255 | if (self.newSemester == "") { 256 | return false 257 | } 258 | } 259 | 260 | return true 261 | } 262 | 263 | func saveSubject() { 264 | var subject: Subject 265 | if (self.subject != nil) { 266 | subject = self.subject! 267 | print(subject) 268 | } else { 269 | print("new subject") 270 | subject = Subject(context: self.managedObjectContext) 271 | } 272 | subject.title = self.title 273 | subject.active = true 274 | subject.simulation = self.simulationChooser == 1 275 | 276 | subject.simMin = self.simMin.floatValue ?? 1 277 | subject.simMax = self.simMax.floatValue ?? 4 278 | 279 | if (self.gradeCounts) { 280 | subject.weight = self.weight.floatValue ?? 1 281 | } else { 282 | subject.weight = Float(0) 283 | } 284 | subject.grade = self.grade.floatValue ?? 1 285 | 286 | var semester: Semester 287 | if (self.semesterChooser == 0) { 288 | semester = self.semesters[self.selectedSemester] 289 | } else { 290 | semester = Semester(context: self.managedObjectContext) 291 | semester.title = self.newSemester 292 | } 293 | 294 | subject.semester = semester 295 | semester.addToSubjects(subject) 296 | 297 | for semester in self.semesters { 298 | if semester.subjectsArray.count < 1 { 299 | managedObjectContext.delete(semester) 300 | } 301 | } 302 | 303 | do { 304 | print(subject.grade) 305 | try self.managedObjectContext.save() 306 | } catch { 307 | print(error) 308 | } 309 | 310 | } 311 | 312 | } 313 | 314 | extension String { 315 | var floatValue: Float? { 316 | if (self.contains(",")) { 317 | return NumberFormatter().number(from: self)?.floatValue 318 | } else { 319 | return Float(self) 320 | } 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /GradeCalc/Views/Subject/SubjectCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SemesterCellView.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 09.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension Float { 13 | var clean: String { 14 | return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self) 15 | } 16 | } 17 | 18 | struct SubjectCellView: View { 19 | 20 | @State var subject: Subject 21 | 22 | @State var refreshing = false 23 | var didSave = NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave) 24 | 25 | var body: some View { 26 | HStack { 27 | Text(subject.title ?? "Unknown") 28 | if (subject.weight != 1.0) { 29 | Text(subject.weight.clean + "x") 30 | .bold() 31 | .font(.footnote) 32 | .padding(.horizontal, 8) 33 | .padding(.vertical, 6) 34 | .background(Color(UIColor(named: "Orange") ?? .orange)) 35 | .cornerRadius(10) 36 | .foregroundColor(.white) 37 | } 38 | refreshing ? Spacer() : Spacer() 39 | if (subject.simulation) { 40 | Text(String(format: "%.2f - %.2f", subject.simMin, subject.simMax)) 41 | .bold() 42 | } else { 43 | Text(String(format: "%.2f", subject.grade)) 44 | .bold() 45 | } 46 | } 47 | .onReceive(self.didSave) { _ in 48 | self.refreshing.toggle() 49 | } 50 | .padding(20) 51 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 60, maxHeight: 60) 52 | .background(Color(UIColor(named: "WhiteBackground") ?? .white)) 53 | .foregroundColor(.primary) 54 | .cornerRadius(16) 55 | .shadow(color: Color(.darkGray).opacity(0.6), radius: 1.4, x: 0, y: 1) 56 | .overlay( 57 | RoundedRectangle(cornerRadius: 16) 58 | .stroke(subject.simulation ? Color(UIColor(named: "Purple") ?? .purple).opacity(0.6) : Color.white.opacity(0), lineWidth: 2) 59 | ) 60 | .opacity(subject.active ? 1.0 : 0.3) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /GradeCalc/Views/Subject/SubjectListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SemesterListView.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 08.03.20. 6 | // Copyright © 2020 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | enum Sheet { 13 | case add, edit 14 | } 15 | 16 | struct SubjectListView: View { 17 | 18 | @Environment(\.managedObjectContext) var managedObjectContext 19 | 20 | @FetchRequest(entity: Semester.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Semester.title, ascending: true)]) var semesters: FetchedResults 21 | 22 | 23 | class SheetMananger: ObservableObject{ 24 | 25 | @Published var showSheet = false 26 | @Published var whichSheet: Sheet = .add 27 | @Published var subject: Subject? = nil 28 | } 29 | 30 | @StateObject var sheetManager = SheetMananger() 31 | 32 | @State private var refreshing = false 33 | private var didSave = NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave) 34 | 35 | @State var menuOpen = false 36 | 37 | @State var simulation = false 38 | 39 | @State var showsSemesterRenameAlert = false 40 | @State var selectedSemester: Semester? 41 | 42 | var topPadding: CGFloat = 0; 43 | 44 | init() { 45 | if #available(iOS 15.0, *) { 46 | topPadding = -20 47 | } 48 | UITableView.appearance().separatorStyle = .none 49 | UITableView.appearance().backgroundColor = UIColor(named: "BlueBackground") 50 | } 51 | 52 | var body: some View { 53 | ZStack { 54 | Rectangle() 55 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 56 | .foregroundColor(.clear) 57 | .background(Color(UIColor(named: "DarkBlueBackground") ?? .blue)) 58 | .opacity(0.8) 59 | .edgesIgnoringSafeArea(.all) 60 | .zIndex(1) 61 | .opacity(self.menuOpen ? 1 : 0) 62 | .onTapGesture { 63 | withAnimation { 64 | self.menuOpen = false 65 | } 66 | } 67 | 68 | VStack { 69 | GradeAverageView(menuOpen: self.$menuOpen, simulation: self.$simulation) 70 | ZStack(alignment: .bottom) { 71 | if semesters.count == 0 { 72 | VStack { 73 | Spacer() 74 | Text("no grades") 75 | .multilineTextAlignment(.center) 76 | Spacer() 77 | } 78 | .zIndex(2) 79 | } 80 | VStack() { 81 | ZStack { 82 | List { 83 | ForEach(self.semesters) { semester in 84 | Button (semester.title ?? "semester") { 85 | self.selectedSemester = semester 86 | self.showsSemesterRenameAlert = true 87 | } 88 | .font(.headline) 89 | .foregroundColor(.primary) 90 | .padding(.horizontal, 10) 91 | .padding(.top, 10) 92 | .padding(.bottom, 5) 93 | .listRowBackground(Color(UIColor(named: "BlueBackground") ?? .blue)) 94 | .hideRowSeparator() 95 | 96 | ForEach(semester.subjectsArray, id: \.self) { subject in 97 | Button(action: { 98 | self.sheetManager.subject = subject 99 | self.sheetManager.whichSheet = .edit 100 | self.sheetManager.showSheet = true 101 | }){ 102 | SubjectCellView(subject: subject) 103 | } 104 | .buttonStyle(BorderlessButtonStyle()) 105 | .contextMenu { 106 | Button(action: { 107 | self.sheetManager.subject = subject 108 | self.sheetManager.whichSheet = .edit 109 | self.sheetManager.showSheet = true 110 | }) { 111 | Text("edit") 112 | Image(systemName: "pencil") 113 | } 114 | Button(action: { 115 | self.toggleActiveState(subject: subject) 116 | }) { 117 | Text(subject.active ? "deactivate" : "activate") 118 | Image(systemName: subject.active ? "xmark" : "checkmark") 119 | } 120 | Spacer() 121 | Button(action: { 122 | self.removeSubject(subject: subject) 123 | }) { 124 | Text("delete") 125 | .foregroundColor(.red) 126 | Image(systemName: "trash") 127 | .foregroundColor(.red) 128 | } 129 | } 130 | } 131 | .onDelete{ row in 132 | self.removeSubject(semester: semester, offsets: row) 133 | } 134 | .onReceive(self.didSave) { _ in 135 | self.refreshing.toggle() 136 | } 137 | .listRowBackground(Color(UIColor(named: "BlueBackground") ?? .blue)) 138 | .hideRowSeparator() 139 | } 140 | Rectangle() 141 | .frame(height: 100) 142 | .foregroundColor(.clear) 143 | .background(Color(UIColor(named: "BlueBackground") ?? .blue)) 144 | .listRowBackground(Color(UIColor(named: "BlueBackground") ?? .blue)) 145 | } 146 | .padding(.top, topPadding) 147 | } 148 | .environment(\.defaultMinListRowHeight, 0) 149 | .cornerRadius(20) 150 | .padding(.top, -30) 151 | .padding(.bottom, -30) 152 | 153 | } 154 | HStack { 155 | Spacer() 156 | Button(action: { 157 | self.sheetManager.whichSheet = .add 158 | self.sheetManager.showSheet = true 159 | }) { 160 | Image(systemName:self.refreshing ? "plus" : "plus") 161 | .font(.system(size: 24, weight: .bold)) 162 | .padding(20) 163 | } 164 | .foregroundColor(Color.white) 165 | .background(LinearGradient(gradient: Gradient(colors: [Color(UIColor(named: "GradientColor1") ?? .blue), Color(UIColor(named: "GradientColor2") ?? .purple)]), startPoint: .topLeading, endPoint: .bottomTrailing)) 166 | .mask(Circle()) 167 | .shadow(color: Color(.black).opacity(0.6), radius: 1.8, x: 0, y: 1) 168 | } 169 | .padding(.horizontal, 20) 170 | .padding(.bottom, 10) 171 | } 172 | .alert(isPresented: self.$showsSemesterRenameAlert, TextAlert(title: NSLocalizedString("rename", comment: "rename"), placeholder: self.selectedSemester?.title ?? "Semester", accept: NSLocalizedString("save", comment: "save"), cancel: NSLocalizedString("cancel", comment: "cancel"), action: { 173 | if let newTitle = $0, let semester = self.selectedSemester { 174 | self.renameSemester(semester: semester, title: newTitle) 175 | } else { 176 | print("Cannot save") 177 | } 178 | })) 179 | } 180 | .sheet(isPresented: self.$sheetManager.showSheet) { 181 | if self.sheetManager.whichSheet == .edit { 182 | SubjectAddView(subject: self.sheetManager.subject,isPresented: self.$sheetManager.showSheet) 183 | .environment(\.managedObjectContext, self.managedObjectContext) 184 | } 185 | if self.sheetManager.whichSheet == .add { 186 | SubjectAddView(subject:nil,isPresented: self.$sheetManager.showSheet) 187 | .environment(\.managedObjectContext, self.managedObjectContext) 188 | } 189 | } 190 | } 191 | 192 | } 193 | 194 | func removeSubject(semester: Semester, offsets: IndexSet) { 195 | for index in offsets { 196 | let subject = semester.subjectsArray[index] 197 | removeSubject(subject: subject) 198 | } 199 | } 200 | 201 | func toggleActiveState(subject: Subject) { 202 | subject.active.toggle() 203 | do { 204 | try managedObjectContext.save() 205 | } catch { 206 | print(error) 207 | } 208 | } 209 | 210 | func renameSemester(semester: Semester, title: String) { 211 | semester.title = title; 212 | do { 213 | try managedObjectContext.save() 214 | } catch { 215 | print(error) 216 | } 217 | } 218 | 219 | func removeSubject(subject: Subject) { 220 | if let semester = subject.semester { 221 | if (semester.subjectsArray.count <= 1) { 222 | managedObjectContext.delete(semester) 223 | } 224 | } 225 | managedObjectContext.delete(subject) 226 | do { 227 | try managedObjectContext.save() 228 | } catch { 229 | print(error) 230 | } 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /GradeCalc/Views/Utils/HideRowSeparatorModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HideRowSeparatorModifier.swift 3 | // GradeCalc 4 | // 5 | // Created by Marlon Lückert on 07.04.22. 6 | // Copyright © 2022 Marlon Lückert. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct HideRowSeparatorModifier: ViewModifier { 13 | 14 | func body(content: Content) -> some View { 15 | if #available(iOS 15.0, *) { 16 | content.listRowSeparator(.hidden) 17 | } else { 18 | content 19 | } 20 | } 21 | } 22 | 23 | extension View { 24 | func hideRowSeparator() -> some View { 25 | modifier(HideRowSeparatorModifier()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /GradeCalc/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | GradeCalc 4 | 5 | Created by Marlon Lückert on 14.03.20. 6 | Copyright © 2020 Marlon Lückert. All rights reserved. 7 | */ 8 | 9 | "grade" = "Note"; 10 | "grade:" = "Note:"; 11 | "best grade:" = "Beste Note:"; 12 | "worst grade:" = "Schlechteste Note:"; 13 | "weight" = "Gewichtung"; 14 | "weight:" = "Gewichtung:"; 15 | "semester" = "Semester"; 16 | "no semester" = "Keine Semester vorhanden"; 17 | "choose semester" = "Semester auswählen"; 18 | "create semester" = "Neues Semester"; 19 | "edit subject" = "Fach bearbeiten"; 20 | "new subject" = "Neues Fach"; 21 | "edit" = "Bearbeiten"; 22 | "save" = "Speichern"; 23 | "delete" = "Löschen"; 24 | "cancel" = "Abbrechen"; 25 | "activate" = "Aktivieren"; 26 | "deactivate" = "Deaktivieren"; 27 | "current average" = "Aktueller Durchschnitt"; 28 | "simulated average" = "Simulierter Durchschnitt"; 29 | "final grade" = "Eingetragene Note"; 30 | "simulated grade" = "Simulierte Note"; 31 | "grade counts" = "Note zählt"; 32 | "title" = "Titel"; 33 | "no grades" = "Noch keine Note eingetragen.\nDrücke den Plus Button, um eine Note hinzuzufügen!"; 34 | "rename" = "Semester umbenennen"; 35 | -------------------------------------------------------------------------------- /GradeCalc/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | GradeCalc 4 | 5 | Created by Marlon Lückert on 14.03.20. 6 | Copyright © 2020 Marlon Lückert. All rights reserved. 7 | */ 8 | 9 | "grade" = "Grade"; 10 | "grade:" = "Grade:"; 11 | "best grade:" = "Best grade:"; 12 | "worst grade:" = "Worst grade:"; 13 | "weight" = "Weight"; 14 | "weight:" = "Weight:"; 15 | "semester" = "Semester"; 16 | "no semester" = "No semesters"; 17 | "choose semester" = "Choose semester"; 18 | "create semester" = "Create semester"; 19 | "edit subject" = "Edit subject"; 20 | "new subject" = "New subject"; 21 | "edit" = "Edit"; 22 | "save" = "Save"; 23 | "delete" = "Delete"; 24 | "cancel" = "Cancel"; 25 | "activate" = "Activate"; 26 | "deactivate" = "Deactivate"; 27 | "current average" = "Your GPA"; 28 | "simulated average" = "Simulated GPA"; 29 | "final grade" = "Final grade"; 30 | "simulated grade" = "Simulated grade"; 31 | "grade counts" = "Grade counts"; 32 | "title" = "Title"; 33 | "no grades" = "Nothing to see here yet.\nPress the plus button to add a new grade!"; 34 | "rename" = "Rename semester"; 35 | 36 | -------------------------------------------------------------------------------- /Media/Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlon360/grade-calc/1beb423f4dcf956b13245de032c0a8a677a26538/Media/Banner.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GradeCalc - GPA Calculator 2 | 3 | [Download at the App Store](https://apps.apple.com/de/app/gradecalc-gpa-calculator/id1502912052) 4 | 5 | ![Banner](Media/Banner.png) 6 | 7 | [Product Hunt](https://www.producthunt.com/posts/gradecalc-gpa-calculator) 8 | 9 | ## Support 10 | 11 | If you want to support the development of this app, feel free to [sponsor](https://github.com/sponsors/marlon360) me or [buy me a coffee](https://ko-fi.com/marlon360). 12 | 13 | [![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Q5Q71M4SL) 14 | 15 | ## Copyright 16 | 17 | This project has no license. That means you are not allowed to sell or distribute this app. 18 | All rights reserved. 19 | © 2020 Marlon Lückert 20 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Privacy Policy 8 | 14 | 15 | 16 | 17 | Privacy Policy 18 |

19 | Marlon Lückert built the Grade Calc app as 20 | a Free app. This SERVICE is provided by 21 | Marlon Lückert at no cost and is intended for use as 22 | is. 23 |

24 |

25 | This page is used to inform visitors regarding my 26 | policies with the collection, use, and disclosure of Personal 27 | Information if anyone decided to use my Service. 28 |

29 |

30 | If you choose to use my Service, then you agree to 31 | the collection and use of information in relation to this 32 | policy. The Personal Information that I collect is 33 | used for providing and improving the Service. I will not use or share your information with 34 | anyone except as described in this Privacy Policy. 35 |

36 |

37 | The terms used in this Privacy Policy have the same meanings 38 | as in our Terms and Conditions, which are accessible at 39 | Grade Calc unless otherwise defined in this Privacy Policy. 40 |

41 |

Information Collection and Use

42 |

43 | For a better experience, while using our Service, I 44 | may require you to provide us with certain personally 45 | identifiable information. The information that 46 | I request will be retained on your device and is not collected by me in any way. 47 |

48 | 49 |

Log Data

50 |

51 | I want to inform you that whenever you 52 | use my Service, in a case of an error in the app 53 | I collect data and information (through third-party 54 | products) on your phone called Log Data. This Log Data may 55 | include information such as your device Internet Protocol 56 | (“IP”) address, device name, operating system version, the 57 | configuration of the app when utilizing my Service, 58 | the time and date of your use of the Service, and other 59 | statistics. 60 |

61 |

Cookies

62 |

63 | Cookies are files with a small amount of data that are 64 | commonly used as anonymous unique identifiers. These are sent 65 | to your browser from the websites that you visit and are 66 | stored on your device's internal memory. 67 |

68 |

69 | This Service does not use these “cookies” explicitly. However, 70 | the app may use third-party code and libraries that use 71 | “cookies” to collect information and improve their services. 72 | You have the option to either accept or refuse these cookies 73 | and know when a cookie is being sent to your device. If you 74 | choose to refuse our cookies, you may not be able to use some 75 | portions of this Service. 76 |

77 |

Service Providers

78 |

79 | I may employ third-party companies and 80 | individuals due to the following reasons: 81 |

82 |
    83 |
  • To facilitate our Service;
  • 84 |
  • To provide the Service on our behalf;
  • 85 |
  • To perform Service-related services; or
  • 86 |
  • To assist us in analyzing how our Service is used.
  • 87 |
88 |

89 | I want to inform users of this Service 90 | that these third parties have access to their Personal 91 | Information. The reason is to perform the tasks assigned to 92 | them on our behalf. However, they are obligated not to 93 | disclose or use the information for any other purpose. 94 |

95 |

Security

96 |

97 | I value your trust in providing us your 98 | Personal Information, thus we are striving to use commercially 99 | acceptable means of protecting it. But remember that no method 100 | of transmission over the internet, or method of electronic 101 | storage is 100% secure and reliable, and I cannot 102 | guarantee its absolute security. 103 |

104 |

Links to Other Sites

105 |

106 | This Service may contain links to other sites. If you click on 107 | a third-party link, you will be directed to that site. Note 108 | that these external sites are not operated by me. 109 | Therefore, I strongly advise you to review the 110 | Privacy Policy of these websites. I have 111 | no control over and assume no responsibility for the content, 112 | privacy policies, or practices of any third-party sites or 113 | services. 114 |

115 |

Children’s Privacy

116 | 117 |
118 |

119 | I do not knowingly collect personally 120 | identifiable information from children. I 121 | encourage all children to never submit any personally 122 | identifiable information through 123 | the Application and/or Services. 124 | I encourage parents and legal guardians to monitor 125 | their children's Internet usage and to help enforce this Policy by instructing 126 | their children never to provide personally identifiable information through the Application and/or Services 127 | without their permission. If you have reason to believe that a child 128 | has provided personally identifiable information to us through the Application and/or Services, 129 | please contact us. You must also be at least 16 years of age to consent to the processing 130 | of your personally identifiable information in your country (in some countries we may allow your parent 131 | or guardian to do so on your behalf). 132 |

133 |
134 |

Changes to This Privacy Policy

135 |

136 | I may update our Privacy Policy from 137 | time to time. Thus, you are advised to review this page 138 | periodically for any changes. I will 139 | notify you of any changes by posting the new Privacy Policy on 140 | this page. 141 |

142 |

This policy is effective as of 2022-01-01

143 |

Contact Us

144 |

145 | If you have any questions or suggestions about my 146 | Privacy Policy, do not hesitate to contact me at m.lueckert@me.com. 147 |

148 |

This privacy policy page was created at privacypolicytemplate.net and modified/generated by App 151 | Privacy Policy Generator

152 | 153 | 154 | --------------------------------------------------------------------------------