🎉 Initial commit

and everything else all at once. There was a woopsie
pull/2/head
Alexis Drai 2 years ago
commit 0e4beec348

120
.gitignore vendored

@ -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/

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Graduator/Graduator.xcodeproj">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -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 = "<group>"; };
EC242B6B2A1F81AE006FE760 /* Subject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subject.swift; sourceTree = "<group>"; };
EC242B6D2A1F81CC006FE760 /* UnitsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsManager.swift; sourceTree = "<group>"; };
EC242B702A1F8283006FE760 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
EC242B742A1F8339006FE760 /* UnitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitView.swift; sourceTree = "<group>"; };
EC242B7A2A1F838C006FE760 /* UnitViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitViewCell.swift; sourceTree = "<group>"; };
EC242B7C2A1F83A1006FE760 /* SubjectViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubjectViewCell.swift; sourceTree = "<group>"; };
EC242B7E2A1F83BF006FE760 /* UnitsManagerVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsManagerVM.swift; sourceTree = "<group>"; };
EC242B822A1FAA9B006FE760 /* Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stub.swift; sourceTree = "<group>"; };
EC242B872A1FC605006FE760 /* NoGradesInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoGradesInfo.swift; sourceTree = "<group>"; };
EC242B892A1FCECA006FE760 /* AverageBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AverageBlockView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
ECC581D52A1D085C006C55EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
ECC581D82A1D085C006C55EF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
ECC581E02A1D08DB006C55EF /* SubjectVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubjectVM.swift; sourceTree = "<group>"; };
ECC581E42A1D0C44006C55EF /* UnitVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitVM.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
EC242B762A1F8345006FE760 /* Views */ = {
isa = PBXGroup;
children = (
EC242B742A1F8339006FE760 /* UnitView.swift */,
);
path = Views;
sourceTree = "<group>";
};
EC242B772A1F834C006FE760 /* Cells */ = {
isa = PBXGroup;
children = (
EC242B7A2A1F838C006FE760 /* UnitViewCell.swift */,
EC242B7C2A1F83A1006FE760 /* SubjectViewCell.swift */,
);
path = Cells;
sourceTree = "<group>";
};
EC242B862A1FC5EC006FE760 /* Bits */ = {
isa = PBXGroup;
children = (
EC242B872A1FC605006FE760 /* NoGradesInfo.swift */,
EC242B892A1FCECA006FE760 /* AverageBlockView.swift */,
);
path = Bits;
sourceTree = "<group>";
};
ECC581C52A1D085B006C55EF = {
isa = PBXGroup;
children = (
ECC581D02A1D085B006C55EF /* Graduator */,
ECC581CF2A1D085B006C55EF /* Products */,
);
sourceTree = "<group>";
};
ECC581CF2A1D085B006C55EF /* Products */ = {
isa = PBXGroup;
children = (
ECC581CE2A1D085B006C55EF /* Graduator.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
ECC581D72A1D085C006C55EF /* Preview Content */ = {
isa = PBXGroup;
children = (
ECC581D82A1D085C006C55EF /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
ECC581DF2A1D08C3006C55EF /* ViewModel */ = {
isa = PBXGroup;
children = (
ECC581E02A1D08DB006C55EF /* SubjectVM.swift */,
ECC581E42A1D0C44006C55EF /* UnitVM.swift */,
EC242B7E2A1F83BF006FE760 /* UnitsManagerVM.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
ECE6E3C02A1F80F6004FE471 /* Model */ = {
isa = PBXGroup;
children = (
EC242B6B2A1F81AE006FE760 /* Subject.swift */,
EC242B692A1F8189006FE760 /* Unit.swift */,
EC242B6D2A1F81CC006FE760 /* UnitsManager.swift */,
);
path = Model;
sourceTree = "<group>";
};
/* 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 */;
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
</Workspace>

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -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)
}
}
}

@ -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)))
}
}

@ -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
}

@ -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]
}

@ -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 })
}
}

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -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
)
]
)
]
}

@ -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")
}
}

@ -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()
}
}
}

@ -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]))
}
}

@ -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]))
}
}

@ -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]))
}
}

@ -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
}
}

@ -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
}
}

@ -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
}
}
*/
}

@ -0,0 +1,2 @@
# Graduator salutes you
Loading…
Cancel
Save