commit 0e4beec348e6d39a8eaa6fd56bd71f3970e1ac49 Author: Alexis Drai Date: Thu May 25 23:25:04 2023 +0200 :tada: Initial commit and everything else all at once. There was a woopsie diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a17a944 --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +# ---> macOS +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# ---> Swift +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + diff --git a/Graduator.xcworkspace/contents.xcworkspacedata b/Graduator.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..03f7d18 --- /dev/null +++ b/Graduator.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Graduator.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Graduator.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Graduator.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Graduator/Graduator.xcodeproj/project.pbxproj b/Graduator/Graduator.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6ff74a0 --- /dev/null +++ b/Graduator/Graduator.xcodeproj/project.pbxproj @@ -0,0 +1,440 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + EC242B6A2A1F8189006FE760 /* Unit.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B692A1F8189006FE760 /* Unit.swift */; }; + EC242B6C2A1F81AE006FE760 /* Subject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B6B2A1F81AE006FE760 /* Subject.swift */; }; + EC242B6E2A1F81CC006FE760 /* UnitsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B6D2A1F81CC006FE760 /* UnitsManager.swift */; }; + EC242B712A1F8283006FE760 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B702A1F8283006FE760 /* MainView.swift */; }; + EC242B752A1F8339006FE760 /* UnitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B742A1F8339006FE760 /* UnitView.swift */; }; + EC242B7B2A1F838C006FE760 /* UnitViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B7A2A1F838C006FE760 /* UnitViewCell.swift */; }; + EC242B7D2A1F83A1006FE760 /* SubjectViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B7C2A1F83A1006FE760 /* SubjectViewCell.swift */; }; + EC242B7F2A1F83BF006FE760 /* UnitsManagerVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B7E2A1F83BF006FE760 /* UnitsManagerVM.swift */; }; + EC242B832A1FAA9B006FE760 /* Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B822A1FAA9B006FE760 /* Stub.swift */; }; + EC242B882A1FC605006FE760 /* NoGradesInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B872A1FC605006FE760 /* NoGradesInfo.swift */; }; + EC242B8A2A1FCECA006FE760 /* AverageBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC242B892A1FCECA006FE760 /* AverageBlockView.swift */; }; + ECC581D22A1D085B006C55EF /* GraduatorApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECC581D12A1D085B006C55EF /* GraduatorApp.swift */; }; + ECC581D62A1D085C006C55EF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ECC581D52A1D085C006C55EF /* Assets.xcassets */; }; + ECC581D92A1D085C006C55EF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ECC581D82A1D085C006C55EF /* Preview Assets.xcassets */; }; + ECC581E12A1D08DB006C55EF /* SubjectVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECC581E02A1D08DB006C55EF /* SubjectVM.swift */; }; + ECC581E52A1D0C44006C55EF /* UnitVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECC581E42A1D0C44006C55EF /* UnitVM.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + EC242B692A1F8189006FE760 /* Unit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Unit.swift; sourceTree = ""; }; + EC242B6B2A1F81AE006FE760 /* Subject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subject.swift; sourceTree = ""; }; + EC242B6D2A1F81CC006FE760 /* UnitsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsManager.swift; sourceTree = ""; }; + EC242B702A1F8283006FE760 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + EC242B742A1F8339006FE760 /* UnitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitView.swift; sourceTree = ""; }; + EC242B7A2A1F838C006FE760 /* UnitViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitViewCell.swift; sourceTree = ""; }; + EC242B7C2A1F83A1006FE760 /* SubjectViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubjectViewCell.swift; sourceTree = ""; }; + EC242B7E2A1F83BF006FE760 /* UnitsManagerVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsManagerVM.swift; sourceTree = ""; }; + EC242B822A1FAA9B006FE760 /* Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stub.swift; sourceTree = ""; }; + EC242B872A1FC605006FE760 /* NoGradesInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoGradesInfo.swift; sourceTree = ""; }; + EC242B892A1FCECA006FE760 /* AverageBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AverageBlockView.swift; sourceTree = ""; }; + ECC581CE2A1D085B006C55EF /* Graduator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Graduator.app; sourceTree = BUILT_PRODUCTS_DIR; }; + ECC581D12A1D085B006C55EF /* GraduatorApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraduatorApp.swift; sourceTree = ""; }; + ECC581D52A1D085C006C55EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + ECC581D82A1D085C006C55EF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + ECC581E02A1D08DB006C55EF /* SubjectVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubjectVM.swift; sourceTree = ""; }; + ECC581E42A1D0C44006C55EF /* UnitVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitVM.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + ECC581CB2A1D085B006C55EF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + EC242B6F2A1F8260006FE760 /* View */ = { + isa = PBXGroup; + children = ( + EC242B862A1FC5EC006FE760 /* Bits */, + EC242B772A1F834C006FE760 /* Cells */, + EC242B762A1F8345006FE760 /* Views */, + ); + path = View; + sourceTree = ""; + }; + EC242B762A1F8345006FE760 /* Views */ = { + isa = PBXGroup; + children = ( + EC242B742A1F8339006FE760 /* UnitView.swift */, + ); + path = Views; + sourceTree = ""; + }; + EC242B772A1F834C006FE760 /* Cells */ = { + isa = PBXGroup; + children = ( + EC242B7A2A1F838C006FE760 /* UnitViewCell.swift */, + EC242B7C2A1F83A1006FE760 /* SubjectViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + EC242B862A1FC5EC006FE760 /* Bits */ = { + isa = PBXGroup; + children = ( + EC242B872A1FC605006FE760 /* NoGradesInfo.swift */, + EC242B892A1FCECA006FE760 /* AverageBlockView.swift */, + ); + path = Bits; + sourceTree = ""; + }; + ECC581C52A1D085B006C55EF = { + isa = PBXGroup; + children = ( + ECC581D02A1D085B006C55EF /* Graduator */, + ECC581CF2A1D085B006C55EF /* Products */, + ); + sourceTree = ""; + }; + ECC581CF2A1D085B006C55EF /* Products */ = { + isa = PBXGroup; + children = ( + ECC581CE2A1D085B006C55EF /* Graduator.app */, + ); + name = Products; + sourceTree = ""; + }; + ECC581D02A1D085B006C55EF /* Graduator */ = { + isa = PBXGroup; + children = ( + ECE6E3C02A1F80F6004FE471 /* Model */, + ECC581DF2A1D08C3006C55EF /* ViewModel */, + EC242B6F2A1F8260006FE760 /* View */, + ECC581D12A1D085B006C55EF /* GraduatorApp.swift */, + EC242B702A1F8283006FE760 /* MainView.swift */, + EC242B822A1FAA9B006FE760 /* Stub.swift */, + ECC581D52A1D085C006C55EF /* Assets.xcassets */, + ECC581D72A1D085C006C55EF /* Preview Content */, + ); + path = Graduator; + sourceTree = ""; + }; + ECC581D72A1D085C006C55EF /* Preview Content */ = { + isa = PBXGroup; + children = ( + ECC581D82A1D085C006C55EF /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + ECC581DF2A1D08C3006C55EF /* ViewModel */ = { + isa = PBXGroup; + children = ( + ECC581E02A1D08DB006C55EF /* SubjectVM.swift */, + ECC581E42A1D0C44006C55EF /* UnitVM.swift */, + EC242B7E2A1F83BF006FE760 /* UnitsManagerVM.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + ECE6E3C02A1F80F6004FE471 /* Model */ = { + isa = PBXGroup; + children = ( + EC242B6B2A1F81AE006FE760 /* Subject.swift */, + EC242B692A1F8189006FE760 /* Unit.swift */, + EC242B6D2A1F81CC006FE760 /* UnitsManager.swift */, + ); + path = Model; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + ECC581CD2A1D085B006C55EF /* Graduator */ = { + isa = PBXNativeTarget; + buildConfigurationList = ECC581DC2A1D085C006C55EF /* Build configuration list for PBXNativeTarget "Graduator" */; + buildPhases = ( + ECC581CA2A1D085B006C55EF /* Sources */, + ECC581CB2A1D085B006C55EF /* Frameworks */, + ECC581CC2A1D085B006C55EF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Graduator; + productName = Graduator; + productReference = ECC581CE2A1D085B006C55EF /* Graduator.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + ECC581C62A1D085B006C55EF /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1420; + LastUpgradeCheck = 1420; + TargetAttributes = { + ECC581CD2A1D085B006C55EF = { + CreatedOnToolsVersion = 14.2; + }; + }; + }; + buildConfigurationList = ECC581C92A1D085B006C55EF /* Build configuration list for PBXProject "Graduator" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = ECC581C52A1D085B006C55EF; + productRefGroup = ECC581CF2A1D085B006C55EF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ECC581CD2A1D085B006C55EF /* Graduator */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + ECC581CC2A1D085B006C55EF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ECC581D92A1D085C006C55EF /* Preview Assets.xcassets in Resources */, + ECC581D62A1D085C006C55EF /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + ECC581CA2A1D085B006C55EF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC242B712A1F8283006FE760 /* MainView.swift in Sources */, + EC242B6E2A1F81CC006FE760 /* UnitsManager.swift in Sources */, + ECC581E52A1D0C44006C55EF /* UnitVM.swift in Sources */, + EC242B7F2A1F83BF006FE760 /* UnitsManagerVM.swift in Sources */, + EC242B7B2A1F838C006FE760 /* UnitViewCell.swift in Sources */, + EC242B6C2A1F81AE006FE760 /* Subject.swift in Sources */, + EC242B8A2A1FCECA006FE760 /* AverageBlockView.swift in Sources */, + ECC581E12A1D08DB006C55EF /* SubjectVM.swift in Sources */, + EC242B6A2A1F8189006FE760 /* Unit.swift in Sources */, + EC242B752A1F8339006FE760 /* UnitView.swift in Sources */, + ECC581D22A1D085B006C55EF /* GraduatorApp.swift in Sources */, + EC242B882A1FC605006FE760 /* NoGradesInfo.swift in Sources */, + EC242B7D2A1F83A1006FE760 /* SubjectViewCell.swift in Sources */, + EC242B832A1FAA9B006FE760 /* Stub.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + ECC581DA2A1D085C006C55EF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + ECC581DB2A1D085C006C55EF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + ECC581DD2A1D085C006C55EF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Graduator/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIStatusBarStyle = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = fr.uca.iut.Graduator; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + ECC581DE2A1D085C006C55EF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Graduator/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIStatusBarStyle = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = fr.uca.iut.Graduator; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + ECC581C92A1D085B006C55EF /* Build configuration list for PBXProject "Graduator" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ECC581DA2A1D085C006C55EF /* Debug */, + ECC581DB2A1D085C006C55EF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ECC581DC2A1D085C006C55EF /* Build configuration list for PBXNativeTarget "Graduator" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ECC581DD2A1D085C006C55EF /* Debug */, + ECC581DE2A1D085C006C55EF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = ECC581C62A1D085B006C55EF /* Project object */; +} diff --git a/Graduator/Graduator.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Graduator/Graduator.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..94b2795 --- /dev/null +++ b/Graduator/Graduator.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,4 @@ + + + diff --git a/Graduator/Graduator/Assets.xcassets/AccentColor.colorset/Contents.json b/Graduator/Graduator/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Graduator/Graduator/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Graduator/Graduator/Assets.xcassets/AppIcon.appiconset/Contents.json b/Graduator/Graduator/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/Graduator/Graduator/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Graduator/Graduator/Assets.xcassets/Contents.json b/Graduator/Graduator/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Graduator/Graduator/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Graduator/Graduator/GraduatorApp.swift b/Graduator/Graduator/GraduatorApp.swift new file mode 100644 index 0000000..b35b8dd --- /dev/null +++ b/Graduator/Graduator/GraduatorApp.swift @@ -0,0 +1,20 @@ +// +// GraduatorApp.swift +// Graduator +// +// Created by etudiant on 2023-05-23. +// + +import SwiftUI + +@main +struct GraduatorApp: App { + @StateObject var unitsManagerVM = UnitsManagerVM() + + var body: some Scene { + WindowGroup { + MainView(unitsManagerVM: unitsManagerVM) + .environmentObject(unitsManagerVM) + } + } +} diff --git a/Graduator/Graduator/MainView.swift b/Graduator/Graduator/MainView.swift new file mode 100644 index 0000000..e77d44c --- /dev/null +++ b/Graduator/Graduator/MainView.swift @@ -0,0 +1,65 @@ +// +// MainView.swift +// graduator +// +// Created by etudiant on 2023-05-23. +// + +import SwiftUI + +struct MainView: View { + @ObservedObject var unitsManagerVM: UnitsManagerVM + + var body: some View { + NavigationStack { + ScrollView() { + VStack(alignment: .leading) { + Divider() + + Text("Blocs") + .font(.title) + .padding(.bottom) + + Text("Vous devez avoir la moyenne à chacun de ces blocs pour avoir votre diplôme.") + .font(.subheadline) + .padding(.bottom) + + if let totalAverage = unitsManagerVM.TotalAverage { + AverageBlockView(average: totalAverage, title: "Moyenne totale") + } else { + NoGradesInfo() + } + + if let professionalAverage = unitsManagerVM.ProfessionalAverage { + AverageBlockView(average: professionalAverage, title: "Moyenne professionnelle") + } else { + NoGradesInfo() + } + + Divider() + + Text("UEs") + .font(.title) + } + .padding() + + VStack(alignment: .leading) { + ForEach(unitsManagerVM.UnitsVM, id: \.model.id) { unitVM in + NavigationLink( + destination: UnitView(unitVM: unitVM)) { + UnitViewCell(unitVM: unitVM) + } + } + } + } + .navigationTitle("Graduator") + } + } +} + +struct MainView_Previews: PreviewProvider { + static var previews: some View { + MainView(unitsManagerVM: UnitsManagerVM(unitsManager: UnitsManager(units: Stub.units))) + } +} + diff --git a/Graduator/Graduator/Model/Subject.swift b/Graduator/Graduator/Model/Subject.swift new file mode 100644 index 0000000..fc1f26e --- /dev/null +++ b/Graduator/Graduator/Model/Subject.swift @@ -0,0 +1,16 @@ +// +// Subject.swift +// Graduator +// +// Created by etudiant on 2023-05-23. +// + +import Foundation + +struct Subject : Identifiable { + let id: UUID + var name: String + var weight: Int + var grade: Double? + var isCalled: Bool +} diff --git a/Graduator/Graduator/Model/Unit.swift b/Graduator/Graduator/Model/Unit.swift new file mode 100644 index 0000000..28f2f6c --- /dev/null +++ b/Graduator/Graduator/Model/Unit.swift @@ -0,0 +1,17 @@ +// +// Unit.swift +// Graduator +// +// Created by etudiant on 2023-05-23. +// + +import Foundation + +struct Unit : Identifiable { + let id: UUID + var name: String + var weight: Int + var isProfessional: Bool + var code: Int + var subjects: [Subject] +} diff --git a/Graduator/Graduator/Model/UnitsManager.swift b/Graduator/Graduator/Model/UnitsManager.swift new file mode 100644 index 0000000..fc9fcae --- /dev/null +++ b/Graduator/Graduator/Model/UnitsManager.swift @@ -0,0 +1,53 @@ +// +// UnitsManager.swift +// Graduator +// +// Created by etudiant on 2023-05-23. +// + +import Foundation + +struct UnitsManager { + + var units: [Unit] + + init(units: [Unit]) { + self.units = units + } + + func getUnits() -> [Unit] { + return units + } + + func getUnit(id: UUID) -> Unit? { + if let index = getIndex(id: id) { + return units[index] + } else { + return nil + } + } + + mutating func addUnit(unit: Unit) -> Unit { + units.append(unit) + return unit + } + + mutating func updateUnit(id: UUID, unit: Unit) -> Unit? { + if let index = getIndex(id: id) { + units[index] = unit + return unit + } else { + return nil + } + } + + mutating func removeUnit(id: UUID) { + if let index = getIndex(id: id) { + units.remove(at: index) + } + } + + private func getIndex(id: UUID) -> Int? { + return units.firstIndex(where: { $0.id == id }) + } +} diff --git a/Graduator/Graduator/Preview Content/Preview Assets.xcassets/Contents.json b/Graduator/Graduator/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Graduator/Graduator/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Graduator/Graduator/Stub.swift b/Graduator/Graduator/Stub.swift new file mode 100644 index 0000000..851a39f --- /dev/null +++ b/Graduator/Graduator/Stub.swift @@ -0,0 +1,236 @@ +// +// Stub.swift +// Graduator +// +// Created by etudiant on 2023-05-25. +// + +import Foundation + +struct Stub { + static let units : [Unit] = [ + Unit( + id: UUID(), + name: "Génie logiciel", + weight: 6, + isProfessional: false, + code: 1, + subjects: [ + Subject( + id: UUID(), + name: "Processus de développement", + weight: 4, + grade: 13.17/20.0, + isCalled: false + ), + Subject( + id: UUID(), + name: "Programmation Orientée Objet", + weight: 9, + grade: 13.63/20.0, + isCalled: false + ), + Subject( + id: UUID(), + name: "Qualité de développement", + weight: 5, + grade: 12.4/20.0, + isCalled: true + ), + Subject( + id: UUID(), + name: "Remise à niveau objets", + weight: 4, + grade: 20.0/20.0, + isCalled: true + ), + ] + ), + Unit( + id: UUID(), + name: "Systèmes et réseaux", + weight: 6, + isProfessional: false, + code: 2, + subjects: [ + Subject( + id: UUID(), + name: "Internet des Objets", + weight: 4, + isCalled: false + ), + Subject( + id: UUID(), + name: "Réseaux", + weight: 4, + grade: 14.5/20.0, + isCalled: true + ), + Subject( + id: UUID(), + name: "Services mobiles", + weight: 4, + isCalled: false + ), + Subject( + id: UUID(), + name: "Système", + weight: 5, + grade: 13.88/20.0, + isCalled: true + ), + ] + ), + Unit( + id: UUID(), + name: "Insertion professionnelle", + weight: 6, + isProfessional: false, + code: 3, + subjects: [ + Subject( + id: UUID(), + name: "Anglais", + weight: 5, + grade: 18.65/20.0, + isCalled: false + ), + Subject( + id: UUID(), + name: "Économie", + weight: 4, + grade: 9.5/20.0, + isCalled: false + ), + Subject( + id: UUID(), + name: "Gestion", + weight: 3, + grade: 9.5/20.0, + isCalled: false + ), + Subject( + id: UUID(), + name: "Communication", + weight: 4, + grade: 17.13/20.0, + isCalled: false + ), + ] + ), + Unit( + id: UUID(), + name: "Technologies mobiles 1", + weight: 9, + isProfessional: false, + code: 4, + subjects: [ + Subject( + id: UUID(), + name: "Android", + weight: 6, + grade: 4.0/20.0, + isCalled: true + ), + Subject( + id: UUID(), + name: "Architecture de projetc C# .NET (1)", + weight: 5, + grade: 14.5/20.0, + isCalled: true + ), + Subject( + id: UUID(), + name: "C++", + weight: 4, + grade: 10.2/20.0, + isCalled: true + ), + Subject( + id: UUID(), + name: "Swift", + weight: 5, + grade: 14.93/20.0, + isCalled: true + ), + ] + ), + Unit( + id: UUID(), + name: "Technologies mobiles 2", + weight: 9, + isProfessional: false, + code: 5, + subjects: [ + Subject( + id: UUID(), + name: "Architecture de projetc C# .NET (2)", + weight: 4, + grade: 12.17/20.0, + isCalled: false + ), + Subject( + id: UUID(), + name: "Client/Serveur", + weight: 4, + isCalled: false + ), + Subject( + id: UUID(), + name: "iOS", + weight: 5, + isCalled: false + ), + Subject( + id: UUID(), + name: "Multiplateformes", + weight: 3, + isCalled: false + ), + Subject( + id: UUID(), + name: "Qt Quick", + weight: 5, + isCalled: false + ), + Subject( + id: UUID(), + name: "MAUI", + weight: 5, + isCalled: false + ), + ] + ), + Unit( + id: UUID(), + name: "Projet", + weight: 9, + isProfessional: true, + code: 6, + subjects: [ + Subject( + id: UUID(), + name: "Projet", + weight: 1, + grade: 13.66/20.0, + isCalled: true + ) + ] + ), + Unit( + id: UUID(), + name: "Stage", + weight: 15, + isProfessional: true, + code: 7, + subjects: [ + Subject( + id: UUID(), + name: "Stage", + weight: 1, + isCalled: false + ) + ] + ) + ] +} diff --git a/Graduator/Graduator/View/Bits/AverageBlockView.swift b/Graduator/Graduator/View/Bits/AverageBlockView.swift new file mode 100644 index 0000000..58730c2 --- /dev/null +++ b/Graduator/Graduator/View/Bits/AverageBlockView.swift @@ -0,0 +1,29 @@ +// +// AverageBlockView.swift +// Graduator +// +// Created by etudiant on 2023-05-25. +// + +import SwiftUI + +struct AverageBlockView: View { + let average: Double + let title: String + + var body: some View { + HStack { + Text(title) + Spacer() + Text(String(format: "%.2f", average * 20)) + Image(systemName: average >= 0.5 ? "graduationcap.fill" : "exclamationmark.bubble.fill") + } + } +} + + +struct AverageBlockView_Previews: PreviewProvider { + static var previews: some View { + AverageBlockView(average: 0.587, title: "Bleep bloop") + } +} diff --git a/Graduator/Graduator/View/Bits/NoGradesInfo.swift b/Graduator/Graduator/View/Bits/NoGradesInfo.swift new file mode 100644 index 0000000..b54d6bf --- /dev/null +++ b/Graduator/Graduator/View/Bits/NoGradesInfo.swift @@ -0,0 +1,18 @@ +// +// NoGradesInfo.swift +// Graduator +// +// Created by etudiant on 2023-05-25. +// + +import SwiftUI + +struct NoGradesInfo: View { + var body: some View { + HStack { + Text("Aucune note enregistrée") + .font(.caption) + Spacer() + } + } +} diff --git a/Graduator/Graduator/View/Cells/SubjectViewCell.swift b/Graduator/Graduator/View/Cells/SubjectViewCell.swift new file mode 100644 index 0000000..02198ec --- /dev/null +++ b/Graduator/Graduator/View/Cells/SubjectViewCell.swift @@ -0,0 +1,114 @@ +// +// SubjectViewCell.swift +// graduator +// +// Created by etudiant on 2023-05-23. +// + +import SwiftUI + +struct SubjectViewCell: View { + @ObservedObject var subjectVM: SubjectVM + + //TODO also allow using the unitview's navigation bar item "Edit" (makes all subjects editable, and more) + @State private var isEditable = false + + private let gradeFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 2 + return formatter + }() + + + var body: some View { + HStack { + if isEditable { + VStack { + Image(systemName: "checkmark.square") + Button(action: { + isEditable = false + subjectVM.onEdited(isCancelled: true) + }) { + Image(systemName: "nosign.app") + .padding(.top, 10.0) + .foregroundColor(.pink) + } + } + } else { + Button(action: { + isEditable = true + subjectVM.onEditing() + }) { + Image(systemName: "lock") + } + } + + VStack { + HStack { + TextField("", text: $subjectVM.model.name) + .disabled(!isEditable) + + + TextField("", value: $subjectVM.model.weight, formatter: NumberFormatter()) + .frame(width: 20) + .disabled(!isEditable) + } + + HStack { + if let grade = subjectVM.model.grade { + Slider(value: Binding( + get: { grade }, + set: { newValue in + if isEditable { + subjectVM.model.grade = newValue + } + } + ), in: 0...1, step: 0.001) + .accentColor(grade < 0.5 ? .red : .green) + .disabled(!isEditable || subjectVM.model.isCalled) + + TextField("", value: Binding( + get: { grade * 20.0 }, + set: { newValue in + if isEditable { + subjectVM.model.grade = newValue / 20.0 + } + } + ), formatter: gradeFormatter) + .frame(width: 50) + .disabled(!isEditable) + + Image(systemName: "snowflake.circle.fill") + .foregroundColor(subjectVM.model.isCalled ? .primary : .gray) + + Toggle("", isOn: $subjectVM.model.isCalled) + .frame(width: 50) + .disabled(!isEditable) + + } else { + NoGradesInfo() + Button(action: { + subjectVM.model.grade = 0.0 + }) { + Image(systemName: "pencil.line") + } + .disabled(!isEditable) + + } + } + } + } + .padding() + .background(Color.gray.opacity(0.2)) + .cornerRadius(12) + .padding(.horizontal) + } +} + + +struct SubjectViewCell_Previews: PreviewProvider { + static var previews: some View { + SubjectViewCell(subjectVM: SubjectVM(subject: Stub.units[0].subjects[0])) + } +} diff --git a/Graduator/Graduator/View/Cells/UnitViewCell.swift b/Graduator/Graduator/View/Cells/UnitViewCell.swift new file mode 100644 index 0000000..fca53cf --- /dev/null +++ b/Graduator/Graduator/View/Cells/UnitViewCell.swift @@ -0,0 +1,48 @@ +// +// UnitViewCell.swift +// graduator +// +// Created by etudiant on 2023-05-23. +// + +import SwiftUI + +struct UnitViewCell: View { + @ObservedObject var unitVM: UnitVM + + var body: some View { + VStack { + + HStack { + Text("UE " + String(unitVM.model.code)) + Text(unitVM.model.name) + Spacer() + Text(String(unitVM.model.weight)) + } + + if let average = unitVM.Average { + HStack { + // TODO add slider linked to "grade" value + // TODO link slider color to the average. If below 10.0, red, else green. + Text("Sliiiiiiiiiiiiider") + .background(Color.red) + Text(String(format: "%.2f", average * 20)) + Spacer() + } + } else { + NoGradesInfo() + } + + } + .padding() + .background(Color.gray.opacity(0.2)) + .cornerRadius(12) + .padding(.horizontal) + } +} + +struct UnitViewCell_Previews: PreviewProvider { + static var previews: some View { + UnitViewCell(unitVM: UnitVM(unit: Stub.units[0])) + } +} diff --git a/Graduator/Graduator/View/Views/UnitView.swift b/Graduator/Graduator/View/Views/UnitView.swift new file mode 100644 index 0000000..87a2eea --- /dev/null +++ b/Graduator/Graduator/View/Views/UnitView.swift @@ -0,0 +1,61 @@ +// +// UnitView.swift +// graduator +// +// Created by etudiant on 2023-05-23. +// + +import SwiftUI + +struct UnitView: View { + @ObservedObject var unitVM: UnitVM + + var body: some View { + VStack(alignment: .leading) { + Text("Unit title") + .font(.title) + .padding() + + UnitViewCell(unitVM: unitVM) + + Divider() + + HStack { + // TODO later add cross image (multiply) + Text("coefficient : " + "weight") + } + .padding(.horizontal) + + HStack { + // TODO later add page with scribbling image + Text("Détail des notes") + } + .padding(.horizontal) + + ScrollView { + ForEach(unitVM.model.subjects) { subjectData in + // You need to convert subjectData into SubjectVM, then use it to create SubjectViewCell + let subjectVM = SubjectVM(subjectData: subjectData) + SubjectViewCell(subjectVM: subjectVM) + } + } + .navigationBarItems(trailing: Button(action: { + // TODO later: Add action for button. Make editable + // * unit weight + // * unit description + // * subjects + // * make all fields editable (just toggle isEditable is the SubjectCellView?) + // * create new subject (creation screen with simple form for name, weight, code, isCalled. Of course, will need to deal with adding it to the unitVM, updating the unitVM, and updating the unitsmanagerVM with the new unitVM. Check the result to make sure that the model does get updated by the VM in the end) + // * delete a subject (again, this has repercusions for the unit and the unitmanager, be careful) + }) { + Text("Edit") + }) + } + } +} + +struct UnitView_Previews: PreviewProvider { + static var previews: some View { + UnitView(unitVM: UnitVM(unit: Stub.units[5])) + } +} diff --git a/Graduator/Graduator/ViewModel/SubjectVM.swift b/Graduator/Graduator/ViewModel/SubjectVM.swift new file mode 100644 index 0000000..5e0b90a --- /dev/null +++ b/Graduator/Graduator/ViewModel/SubjectVM.swift @@ -0,0 +1,92 @@ +// +// SubjectVM.swift +// Graduator +// +// Created by etudiant on 2023-05-23. +// + +import Foundation + +extension Subject { + struct Data: Identifiable { + let id: UUID + var name: String + var weight: Int + var grade: Double? + var isCalled: Bool + } + + init(subjectData: Subject.Data) { + self.id = subjectData.id + self.name = subjectData.name + self.weight = subjectData.weight + self.grade = subjectData.grade + self.isCalled = subjectData.isCalled + } + + var data: Data { + Data( + id: self.id, + name: self.name, + weight: self.weight, + grade: self.grade, + isCalled: self.isCalled + ) + } + + mutating func update(from data: Data) { + // FIXME improve the guard + guard (data.id == self.data.id + && (self.isCalled == false || data.isCalled == false) + && (data.grade != nil || data.isCalled == false)) + else { return } + + self.name = data.name + self.weight = data.weight + self.grade = data.grade + self.isCalled = data.isCalled + } +} + +class SubjectVM : ObservableObject { + var original: Subject + @Published var model: Subject.Data + @Published var isEdited: Bool = false + + init(subject: Subject) { + original = subject + model = original.data + } + + init(subjectData: Subject.Data) { + self.original = Subject(subjectData: subjectData) + self.model = subjectData + } + + convenience init() { + self.init(subject: Subject( + id: UUID(), + name: "", + weight: 1, + grade: 10.0, + isCalled: false + )) + } + + func onEditing() { + model = original.data + isEdited = true + } + + // TODO add suitable error handling for cases where the user enters an invalid number. (negative numbers are forbidden... Do we need to guard against NaN and nil as well?) + func onEdited(isCancelled: Bool = false) { + if(!isCancelled && isEdited){ + original.update(from: model) + } + isEdited = false + } + + var HasGrade: Bool { + return model.grade != nil + } +} diff --git a/Graduator/Graduator/ViewModel/UnitVM.swift b/Graduator/Graduator/ViewModel/UnitVM.swift new file mode 100644 index 0000000..d41d29b --- /dev/null +++ b/Graduator/Graduator/ViewModel/UnitVM.swift @@ -0,0 +1,107 @@ +// +// UnitVM.swift +// Graduator +// +// Created by etudiant on 2023-05-23. +// + +import Foundation + +extension Unit { + struct Data: Identifiable { + let id: UUID + var name: String + var weight: Int + var isProfessional: Bool + var code: Int + public var subjects: [Subject.Data] = [] + } + + var data: Data { + Data( + id: self.id, + name: self.name, + weight: self.weight, + isProfessional: self.isProfessional, + code: self.code, + subjects: self.subjects.map{ $0.data } + ) + } + + + // TODO ? Maybe check that we're not setting isCalled to true on a subject with a nil grade. Keep in mind that's what we already did in SubjectVM's update function + mutating func update(from data: Data) { + guard self.id == data.id else {return} + self.name = data.name + self.weight = data.weight + self.isProfessional = data.isProfessional + self.code = data.code + self.subjects = data.subjects.map { + Subject( + id: $0.id, + name: $0.name, + weight: $0.weight, + grade: $0.grade, + isCalled: $0.isCalled + ) + } + } +} + +class UnitVM : ObservableObject { + var original: Unit + @Published var model: Unit.Data + @Published var isEdited: Bool = false + + + init(unit: Unit) { + original = unit + model = original.data + } + + convenience init() { + self.init(unit: Unit( + id: UUID(), + name: "", + weight: 1, + isProfessional: false, + code: -1, + subjects: [] + )) + } + + func onEditing() { + model = original.data + isEdited = true + } + + func onEdited(isCancelled: Bool = false) { + if(!isCancelled && isEdited){ + original.update(from: model) + } + isEdited = false + } + + // TODO Maybe move this to the model? + var Average: Double? { + var totalWeight = 0 + var weightedSum = 0.0 + + for subject in model.subjects { + if let grade = subject.grade { + totalWeight += subject.weight + weightedSum += grade * Double(subject.weight) + } + } + + guard totalWeight > 0 else { return nil } + + return weightedSum / Double(totalWeight) + } + + var isCalled: Bool { + // FIXME this is false if any suject therein has a nil grade. This is true if all subjects therein are locked + // also check if this can stay a function, or if it would be better as a calculated property + return false + } +} diff --git a/Graduator/Graduator/ViewModel/UnitsManagerVM.swift b/Graduator/Graduator/ViewModel/UnitsManagerVM.swift new file mode 100644 index 0000000..b7b736f --- /dev/null +++ b/Graduator/Graduator/ViewModel/UnitsManagerVM.swift @@ -0,0 +1,118 @@ +// +// UnitsManagerVM.swift +// Graduator +// +// Created by etudiant on 2023-05-25. +// + +import Foundation + +extension UnitsManager { + struct Data { + var units: [Unit.Data] = [] + } + + var data: Data { + Data( + units: self.getUnits().map{ $0.data } + ) + } + + mutating func update(from data: Data) { + self.units = data.units.map{ + Unit( + id: $0.id, + name: $0.name, + weight: $0.weight, + isProfessional: $0.isProfessional, + code: $0.code, + subjects: $0.subjects.map { + Subject( + id: $0.id, + name: $0.name, + weight: $0.weight, + grade: $0.grade, + isCalled: $0.isCalled + ) + } + ) + } + } +} + + +class UnitsManagerVM : ObservableObject { + + var original: UnitsManager + @Published var model: UnitsManager.Data + @Published var isEdited: Bool = false + + private var unitsVM: [UnitVM] + + public var UnitsVM: [UnitVM] { + unitsVM + } + + init(unitsManager: UnitsManager) { + original = unitsManager + model = original.data + unitsVM = unitsManager.getUnits().map { + UnitVM(unit: $0) + } + } + + convenience init() { + self.init(unitsManager: UnitsManager(units: Stub.units)) + } + + func onEditing() { + model = original.data + isEdited = true + } + + func onEdited(isCancelled: Bool = false) { + if(!isCancelled && isEdited){ + original.update(from: model) + } + isEdited = false + } + + var TotalAverage: Double? { + return getAverage(unitsVM: self.unitsVM) + } + + var ProfessionalAverage: Double? { + return getAverage(unitsVM: self.unitsVM.filter { $0.model.isProfessional }) + } + + private func getAverage(unitsVM: [UnitVM]) -> Double? { + var totalWeight = 0 + var weightedSum = 0.0 + + for unitVM in unitsVM { + if let grade = unitVM.Average { + totalWeight += unitVM.model.weight + weightedSum += grade * Double(unitVM.model.weight) + } + } + + guard totalWeight > 0 else { return nil } + + return weightedSum / Double(totalWeight) + } + + // FIXME fix this nightmare after resting and doing Subject first + /* + func updateUnit(id: UUID, unitVM: UnitVM) -> UnitVM? { + if let index = unitsVM.firstIndex(where: { $0.model.id == id }) { + original.updateUnit(id: id, unit: Unit(unitsVM[index].original)) + return unitVM + } else { + return nil + } + } + */ +} + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..870d500 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Graduator salutes you +