diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml index d2d579ce3..15da04520 100644 --- a/.github/workflows/swiftlint.yml +++ b/.github/workflows/swiftlint.yml @@ -2,7 +2,6 @@ name: SwiftLint on: workflow_dispatch: - pull_request: jobs: @@ -11,8 +10,6 @@ jobs: runs-on: macos-latest concurrency: - # When running on develop, use the sha to allow all runs of this workflow to run concurrently. - # Otherwise only allow a single run of this workflow on each branch, automatically cancelling older runs. group: ${{ github.ref == 'refs/heads/develop' && format('swiftlint-develop-{0}', github.sha) || format('swiftlint-{0}', github.ref) }} cancel-in-progress: true @@ -29,9 +26,22 @@ jobs: ${{ runner.os }}-gems- - name: Setup environment - run: + run: | source ci_scripts/ci_prepare_env.sh && setup_github_actions_environment + xcodes select 16.1 - name: SwiftLint - run: - bundle exec fastlane linting + run: | + export PODS_ROOT=$(pwd)/Pods + "${PODS_ROOT}/SwiftLint/swiftlint" --reporter sarif > swiftlint.report.sarif + + - name: Prepare swiftlint.report.sarif + if: success() || failure() + run: | + swift PrepareSarifToUpload.swift + + - name: Upload report + uses: github/codeql-action/upload-sarif@v3 + if: success() || failure() + with: + sarif_file: swiftlint.report.sarif diff --git a/.swiftlint.yml b/.swiftlint.yml index 4ed67e1a6..391feb612 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,4 @@ +warning_threshold: 1 disabled_rules: # rule identifiers to exclude from running - identifier_name - comment_spacing @@ -31,6 +32,7 @@ excluded: # paths to ignore during linting. Takes precedence over `included`. - Core/Core/SwiftGen - Authorization/Authorization/SwiftGen - Course/Course/SwiftGen + - Discussion/Discussion/SwiftGen - Discovery/Discovery/SwiftGen - Dashboard/Dashboard/SwiftGen - Profile/Profile/SwiftGen @@ -48,14 +50,15 @@ excluded: # paths to ignore during linting. Takes precedence over `included`. force_try: error line_length: 120 -type_body_length: 300 +type_body_length: 600 +function_body_length: 100 trailing_whitespace: ignores_empty_lines: true file_length: - warning: 500 - error: 1200 + warning: 850 + error: 1500 function_parameter_count: warning: 10 @@ -70,11 +73,4 @@ type_name: - iPhone - API -identifier_name: - min_length: # only min_length - error: 1 # only error - # excluded: # excluded via string array -# - id -# - URL -# - GlobalAPIKey reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) diff --git a/Authorization/Authorization.xcodeproj/xcshareddata/xcschemes/Authorization.xcscheme b/Authorization/Authorization.xcodeproj/xcshareddata/xcschemes/Authorization.xcscheme index aef1b20d0..138a67b67 100644 --- a/Authorization/Authorization.xcodeproj/xcshareddata/xcschemes/Authorization.xcscheme +++ b/Authorization/Authorization.xcodeproj/xcshareddata/xcschemes/Authorization.xcscheme @@ -1,6 +1,6 @@ OpenURLAction.Result { viewModel.router.showWebBrowser(title: "", url: url) return .handled diff --git a/Authorization/Authorization/Presentation/SSO/SSOWebView.swift b/Authorization/Authorization/Presentation/SSO/SSOWebView.swift index d7a89cf7b..b525f91df 100644 --- a/Authorization/Authorization/Presentation/SSO/SSOWebView.swift +++ b/Authorization/Authorization/Presentation/SSO/SSOWebView.swift @@ -61,7 +61,10 @@ public struct SSOWebView: UIViewRepresentable { } // WKScriptMessageHandler - public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + public func userContentController( + _ userContentController: WKUserContentController, + didReceive message: WKScriptMessage + ) { } // WKNavigationDelegate @@ -71,7 +74,11 @@ public struct SSOWebView: UIViewRepresentable { } } - public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + public func webView( + _ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void + ) { guard let url = webView.url?.absoluteString else { decisionHandler(.allow) return diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 4f2728434..296f61cf1 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -108,7 +108,7 @@ 070019AC28F6FD0100D5FC78 /* CourseDetailBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070019AB28F6FD0100D5FC78 /* CourseDetailBlock.swift */; }; 070019AE28F701B200D5FC78 /* Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070019AD28F701B200D5FC78 /* Certificate.swift */; }; 071009C428D1C9D000344290 /* StyledButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 071009C328D1C9D000344290 /* StyledButton.swift */; }; - 071009D028D1E3A600344290 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 071009CF28D1E3A600344290 /* Constants.swift */; }; + 071009D028D1E3A600344290 /* AuthConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 071009CF28D1E3A600344290 /* AuthConstants.swift */; }; 0716946D296D996900E3DED6 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0770DE0828D07831006D8A5D /* Core.framework */; platformFilter = ios; }; 0727877028D23411002E9142 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0727876F28D23411002E9142 /* Config.swift */; }; 0727877728D23847002E9142 /* DataLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0727877628D23847002E9142 /* DataLayer.swift */; }; @@ -156,6 +156,7 @@ CE57127C2CD109DB00D4AB17 /* OEXFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CE57127B2CD109DB00D4AB17 /* OEXFoundation */; }; CE7CAF392CC1561E00E0AC9D /* OEXFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CE7CAF382CC1561E00E0AC9D /* OEXFoundation */; }; CE953A3B2CD0DA940023D667 /* CoreMock.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE953A3A2CD0DA940023D667 /* CoreMock.generated.swift */; }; + CED42FFC2D099F0400C7AD89 /* DownloadManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED42FFB2D099F0400C7AD89 /* DownloadManagerMock.swift */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */; }; DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */; }; @@ -301,7 +302,7 @@ 070019AB28F6FD0100D5FC78 /* CourseDetailBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetailBlock.swift; sourceTree = ""; }; 070019AD28F701B200D5FC78 /* Certificate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Certificate.swift; sourceTree = ""; }; 071009C328D1C9D000344290 /* StyledButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyledButton.swift; sourceTree = ""; }; - 071009CF28D1E3A600344290 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 071009CF28D1E3A600344290 /* AuthConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConstants.swift; sourceTree = ""; }; 07169469296D996900E3DED6 /* CoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 0727876F28D23411002E9142 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 0727877628D23847002E9142 /* DataLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataLayer.swift; sourceTree = ""; }; @@ -365,6 +366,7 @@ CE1D5B7C2CE65D360019CA34 /* Protected.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Protected.swift; sourceTree = ""; }; CE54C2D12CC80D8500E529F9 /* DownloadManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManagerTests.swift; sourceTree = ""; }; CE953A3A2CD0DA940023D667 /* CoreMock.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMock.generated.swift; sourceTree = ""; }; + CED42FFB2D099F0400C7AD89 /* DownloadManagerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManagerMock.swift; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConfig.swift; sourceTree = ""; }; DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreementConfig.swift; sourceTree = ""; }; @@ -665,7 +667,7 @@ CE4AB5942CE2504500E27A00 /* System */, 0770DE5D28D0B209006D8A5D /* Localizable.strings */, 0770DE5128D0ADFF006D8A5D /* Assets.xcassets */, - 071009CF28D1E3A600344290 /* Constants.swift */, + 071009CF28D1E3A600344290 /* AuthConstants.swift */, ); path = Core; sourceTree = ""; @@ -677,6 +679,7 @@ 0236961E28F9A2F600EEF206 /* AuthEndpoint.swift */, 029A132B2C2471F8005FB830 /* OfflineSyncEndpoint.swift */, 02A4833929B8A9AB00D33F33 /* DownloadManager.swift */, + CED42FFB2D099F0400C7AD89 /* DownloadManagerMock.swift */, 029A13272C246AE6005FB830 /* OfflineSyncManager.swift */, ); path = Network; @@ -935,8 +938,8 @@ buildConfigurationList = 0770DE0F28D07831006D8A5D /* Build configuration list for PBXNativeTarget "Core" */; buildPhases = ( ED83AD5255805030E042D62A /* [CP] Check Pods Manifest.lock */, - 0770DE5A28D0B1E5006D8A5D /* SwiftGen */, 0770DE0328D07831006D8A5D /* Headers */, + 0770DE5A28D0B1E5006D8A5D /* SwiftGen */, 0770DE0428D07831006D8A5D /* Sources */, 0770DE0528D07831006D8A5D /* Frameworks */, 0770DE0628D07831006D8A5D /* Resources */, @@ -963,7 +966,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1610; TargetAttributes = { 07169468296D996800E3DED6 = { CreatedOnToolsVersion = 14.2; @@ -1209,7 +1212,7 @@ 02E224DB2BB76B3E00EF1ADB /* DynamicOffsetView.swift in Sources */, 0259104A29C4A5B6004B5A55 /* UserSettings.swift in Sources */, 021D925028DC89D100ACC565 /* UserProfile.swift in Sources */, - 071009D028D1E3A600344290 /* Constants.swift in Sources */, + 071009D028D1E3A600344290 /* AuthConstants.swift in Sources */, 0770DE1928D0847D006D8A5D /* BaseRouter.swift in Sources */, 02286D162C106393005EEC8D /* CourseDates.swift in Sources */, 0284DBFE28D48C5300830893 /* CourseItem.swift in Sources */, @@ -1260,6 +1263,7 @@ A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */, 02280F5B294B4E6F0032823A /* Connectivity.swift in Sources */, 0254D1912BCD699F000CDE89 /* RefreshProgressView.swift in Sources */, + CED42FFC2D099F0400C7AD89 /* DownloadManagerMock.swift in Sources */, 02066B482906F73400F4307E /* PickerMenu.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1325,6 +1329,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1356,6 +1361,7 @@ baseConfigurationReference = 0E13E9173C9C4CFC19F8B6F2 /* Pods-App-Core.debugstage.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1363,6 +1369,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1373,6 +1380,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Core; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1447,6 +1455,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1472,6 +1481,7 @@ baseConfigurationReference = 0754BB7841E3C0F8D6464951 /* Pods-App-Core.releasestage.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1479,6 +1489,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1489,6 +1500,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Core; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1696,6 +1708,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1727,6 +1740,7 @@ baseConfigurationReference = 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1734,6 +1748,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1744,6 +1759,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Core; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1795,6 +1811,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1820,6 +1837,7 @@ baseConfigurationReference = 1A154A95AF4EE85A4A1C083B /* Pods-App-Core.releasedev.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1827,6 +1845,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1837,6 +1856,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Core; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1887,6 +1907,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1918,6 +1939,7 @@ baseConfigurationReference = C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1925,6 +1947,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1935,6 +1958,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Core; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1986,6 +2010,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -2011,6 +2036,7 @@ baseConfigurationReference = 3B74C6685E416657F3C5F5A8 /* Pods-App-Core.releaseprod.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -2018,6 +2044,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -2028,6 +2055,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Core; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -2078,6 +2106,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -2142,6 +2171,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -2167,6 +2197,7 @@ baseConfigurationReference = 2B7E6FE7843FC4CF2BFA712D /* Pods-App-Core.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -2174,6 +2205,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -2184,6 +2216,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Core; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -2202,6 +2235,7 @@ baseConfigurationReference = 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -2209,6 +2243,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -2219,6 +2254,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.Core; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/Core/Core.xcodeproj/xcshareddata/xcschemes/Core.xcscheme b/Core/Core.xcodeproj/xcshareddata/xcschemes/Core.xcscheme index e5be24754..97edb3b27 100644 --- a/Core/Core.xcodeproj/xcshareddata/xcschemes/Core.xcscheme +++ b/Core/Core.xcodeproj/xcshareddata/xcschemes/Core.xcscheme @@ -1,6 +1,6 @@ = sevenDaysAgo { - guard let daysAgo = calendar.dateComponents([.day], from: startOfSelfDate, to: startOfCurrentDate).day else { + guard let daysAgo = calendar.dateComponents( + [.day], + from: startOfSelfDate, + to: startOfCurrentDate + ).day else { return self.dateToString(style: .mmddyy, useRelativeDates: false) } return CoreLocalization.Date.daysAgo(daysAgo) @@ -94,7 +97,7 @@ public extension Date { specificFormatter.dateFormat = isCurrentYear ? "MMMM d" : "MMMM d, yyyy" return dueInString + specificFormatter.string(from: self) } - + func isDateInNextWeek(date: Date, currentDate: Date) -> Bool { let calendar = Calendar.current guard let nextWeek = calendar.date(byAdding: .weekOfYear, value: 1, to: currentDate) else { return false } @@ -118,19 +121,19 @@ public extension Date { } self = date } - - init(milliseconds: Double) { - let now = Date() - let calendar = Calendar.current - var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: now) - components.nanosecond = Int((milliseconds.truncatingRemainder(dividingBy: 1)) * 1000000) - let seconds = Int(milliseconds) - components.second = seconds % 60 - components.minute = (seconds / 60) % 60 - components.hour = (seconds / 3600) % 24 - let date = calendar.date(from: components) ?? Date() - self = date - } + + init(milliseconds: Double) { + let now = Date() + let calendar = Calendar.current + var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: now) + components.nanosecond = Int((milliseconds.truncatingRemainder(dividingBy: 1)) * 1000000) + let seconds = Int(milliseconds) + components.second = seconds % 60 + components.minute = (seconds / 60) % 60 + components.hour = (seconds / 3600) % 24 + let date = calendar.date(from: components) ?? Date() + self = date + } } public enum DateStringStyle { @@ -150,11 +153,11 @@ public extension Date { func secondsSinceMidnight() -> Double { let calendar = Calendar.current let components = calendar.dateComponents([.hour, .minute, .second], from: self) - + guard let hours = components.hour, let minutes = components.minute, let seconds = components.second else { return 0.0 } - + let totalSeconds = Double(hours) * 3600.0 + Double(minutes) * 60.0 + Double(seconds) return totalSeconds } @@ -190,7 +193,7 @@ public extension Date { } let date = dateFormatter.string(from: self) - + switch style { case .courseStartsMonthDDYear: return CoreLocalization.Date.courseStarts + " " + date @@ -226,17 +229,12 @@ public extension Date { case .shortWeekdayMonthDayYear: return ( dueIn ? CoreLocalization.Date.dueIn : "" - ) + getShortWeekdayMonthDayYear(dateFormatterString: date) + ) + date } } private func applyShortWeekdayMonthDayYear(dateFormatter: DateFormatter) { - dateFormatter.dateFormat = "MMMM d, yyyy" - } - - private func getShortWeekdayMonthDayYear(dateFormatterString: String) -> String { - let days = Calendar.current.dateComponents([.day], from: self, to: Date()) - return dateFormatterString + dateFormatter.dateFormat = "MMMM d, yyyy" } func isCurrentYear() -> Bool { @@ -250,7 +248,7 @@ public extension Date { func isEarlierThanOrEqualTo(date: Date) -> Bool { timeIntervalSince1970 <= date.timeIntervalSince1970 } - + func isLaterThanOrEqualTo(date: Date) -> Bool { timeIntervalSince1970 >= date.timeIntervalSince1970 } diff --git a/Core/Core/Network/AuthEndpoint.swift b/Core/Core/Network/AuthEndpoint.swift index d5fe55296..2ec1a78ad 100644 --- a/Core/Core/Network/AuthEndpoint.swift +++ b/Core/Core/Network/AuthEndpoint.swift @@ -67,7 +67,7 @@ enum AuthEndpoint: EndPointType { switch self { case let .getAccessToken(username, password, clientId, tokenType): let params: [String: Encodable & Sendable] = [ - "grant_type": Constants.GrantTypePassword, + "grant_type": AuthConstants.GrantTypePassword, "client_id": clientId, "username": username, "password": password, diff --git a/Core/Core/Network/DownloadManager.swift b/Core/Core/Network/DownloadManager.swift index 0c7b7ee8a..47bef7d17 100644 --- a/Core/Core/Network/DownloadManager.swift +++ b/Core/Core/Network/DownloadManager.swift @@ -158,7 +158,7 @@ public class DownloadManager: DownloadManagerProtocol { private let connectivity: ConnectivityProtocol private var downloadRequest: DownloadRequest? private var isDownloadingInProgress: Bool = false - private nonisolated(unsafe) var currentDownloadEventPublisher: PassthroughSubject = .init() + private nonisolated(unsafe) var currentDownloadEventPublisher = PassthroughSubject() private let backgroundTaskProvider = BackgroundTaskProvider() private var cancellables = Set() private nonisolated(unsafe) var failedDownloads: [DownloadDataTask] = [] @@ -436,7 +436,6 @@ public class DownloadManager: DownloadManagerProtocol { self.failedDownloads = [] } } - print(">>> IS NIL") return } if !connectivity.isInternetAvaliable { @@ -467,9 +466,7 @@ public class DownloadManager: DownloadManagerProtocol { } private func downloadFileWithProgress(_ download: DownloadDataTask) async throws { - guard let url = URL(string: download.url), let folderURL = self.filesFolderUrl else { - return - } + guard let url = URL(string: download.url), let folderURL = self.filesFolderUrl else { return } await persistence.updateDownloadState( id: download.id, @@ -495,8 +492,7 @@ public class DownloadManager: DownloadManagerProtocol { self.currentDownloadTask?.progress = fractionCompleted self.currentDownloadTask?.state = .inProgress self.currentDownloadEventPublisher.send(.progress(fractionCompleted, download)) - let completed = Double(fractionCompleted * 100) - debugLog(">>>>> Downloading File", download.url, completed, "%") + debugLog(">>>>> Downloading File", download.url, Double(fractionCompleted * 100), "%") } downloadRequest?.responseURL { [weak self] response in @@ -526,9 +522,7 @@ public class DownloadManager: DownloadManagerProtocol { } private func downloadHTMLWithProgress(_ download: DownloadDataTask) async throws { - guard let url = URL(string: download.url), let folderURL = self.filesFolderUrl else { - return - } + guard let url = URL(string: download.url), let folderURL = self.filesFolderUrl else { return } await persistence.updateDownloadState( id: download.id, @@ -555,8 +549,7 @@ public class DownloadManager: DownloadManagerProtocol { self.currentDownloadTask?.progress = fractionCompleted self.currentDownloadTask?.state = .inProgress self.currentDownloadEventPublisher.send(.progress(fractionCompleted, download)) - let completed = Double(fractionCompleted * 100) - debugLog(">>>>> Downloading HTML", download.url, completed, "%") + debugLog(">>>>> Downloading HTML", download.url, Double(fractionCompleted * 100), "%") } downloadRequest?.responseURL { [weak self] response in @@ -829,81 +822,3 @@ public final class BackgroundTaskProvider: @unchecked Sendable { } } } - -// Mark - For testing and SwiftUI preview -// swiftlint:disable file_length -#if DEBUG -public class DownloadManagerMock: DownloadManagerProtocol { - - public init() {} - - public func updateUnzippedFileSize(for sequentials: [CourseSequential]) -> [CourseSequential] {[]} - - public var currentDownloadTask: DownloadDataTask? { - return nil - } - - public func publisher() -> AnyPublisher { - return Just(1).eraseToAnyPublisher() - } - - public func eventPublisher() -> AnyPublisher { - return Just( - .canceled( - .init( - id: "", - blockId: "", - courseId: "", - userId: 0, - url: "", - fileName: "", - displayName: "", - progress: 1, - resumeData: nil, - state: .inProgress, - type: .video, - fileSize: 0, - lastModified: "" - ) - ) - ).eraseToAnyPublisher() - } - - public func addToDownloadQueue(blocks: [CourseBlock]) {} - - public func getDownloadTasks() -> [DownloadDataTask] { - [] - } - - public func getDownloadTasksForCourse(_ courseId: String) async -> [DownloadDataTask] { - await withCheckedContinuation { continuation in - continuation.resume(returning: []) - } - } - - public func cancelDownloading(courseId: String, blocks: [CourseBlock]) async throws {} - - public func cancelDownloading(task: DownloadDataTask) {} - - public func cancelDownloading(courseId: String) async {} - - public func cancelAllDownloading() async throws {} - - public func resumeDownloading() {} - - public func deleteFile(blocks: [CourseBlock]) {} - - public func deleteAllFiles() {} - - public func fileUrl(for blockId: String) -> URL? { - return nil - } - - public func isLargeVideosSize(blocks: [CourseBlock]) -> Bool { - false - } - - public func removeAppSupportDirectoryUnusedContent() {} -} -#endif -// swiftlint:enable file_length diff --git a/Core/Core/Network/DownloadManagerMock.swift b/Core/Core/Network/DownloadManagerMock.swift new file mode 100644 index 000000000..f7889df09 --- /dev/null +++ b/Core/Core/Network/DownloadManagerMock.swift @@ -0,0 +1,85 @@ +// +// DownloadManagerMock.swift +// Core +// +// Created by Ivan Stepanok on 11.12.2024. +// + +import Foundation +import Combine + +// Mark - For testing and SwiftUI preview +#if DEBUG +public class DownloadManagerMock: DownloadManagerProtocol { + + public init() {} + + public func updateUnzippedFileSize(for sequentials: [CourseSequential]) -> [CourseSequential] {[]} + + public var currentDownloadTask: DownloadDataTask? { + return nil + } + + public func publisher() -> AnyPublisher { + return Just(1).eraseToAnyPublisher() + } + + public func eventPublisher() -> AnyPublisher { + return Just( + .canceled( + .init( + id: "", + blockId: "", + courseId: "", + userId: 0, + url: "", + fileName: "", + displayName: "", + progress: 1, + resumeData: nil, + state: .inProgress, + type: .video, + fileSize: 0, + lastModified: "" + ) + ) + ).eraseToAnyPublisher() + } + + public func addToDownloadQueue(blocks: [CourseBlock]) {} + + public func getDownloadTasks() -> [DownloadDataTask] { + [] + } + + public func getDownloadTasksForCourse(_ courseId: String) async -> [DownloadDataTask] { + await withCheckedContinuation { continuation in + continuation.resume(returning: []) + } + } + + public func cancelDownloading(courseId: String, blocks: [CourseBlock]) async throws {} + + public func cancelDownloading(task: DownloadDataTask) {} + + public func cancelDownloading(courseId: String) async {} + + public func cancelAllDownloading() async throws {} + + public func resumeDownloading() {} + + public func deleteFile(blocks: [CourseBlock]) {} + + public func deleteAllFiles() {} + + public func fileUrl(for blockId: String) -> URL? { + return nil + } + + public func isLargeVideosSize(blocks: [CourseBlock]) -> Bool { + false + } + + public func removeAppSupportDirectoryUnusedContent() {} +} +#endif diff --git a/Core/Core/Network/RequestInterceptor.swift b/Core/Core/Network/RequestInterceptor.swift index 73fe5970c..d6bb9b8cd 100644 --- a/Core/Core/Network/RequestInterceptor.swift +++ b/Core/Core/Network/RequestInterceptor.swift @@ -43,11 +43,16 @@ final public class RequestInterceptor: Alamofire.RequestInterceptor { let userAgent: String = { if let info = Bundle.main.infoDictionary { - let executable: AnyObject = info[kCFBundleExecutableKey as String] as AnyObject? ?? "Unknown" as AnyObject - let bundle: AnyObject = info[kCFBundleIdentifierKey as String] as AnyObject? ?? "Unknown" as AnyObject - let version: AnyObject = info["CFBundleShortVersionString"] as AnyObject? ?? "Unknown" as AnyObject + let executable: AnyObject = info[kCFBundleExecutableKey as String] as AnyObject? + ?? "Unknown" as AnyObject + let bundle: AnyObject = info[kCFBundleIdentifierKey as String] as AnyObject? + ?? "Unknown" as AnyObject + let version: AnyObject = info["CFBundleShortVersionString"] as AnyObject? + ?? "Unknown" as AnyObject let os: AnyObject = ProcessInfo.processInfo.operatingSystemVersionString as AnyObject - var mutableUserAgent = NSMutableString(string: "\(executable)/\(bundle) (\(version); OS \(os))") as CFMutableString + let mutableUserAgent = NSMutableString( + string: "\(executable)/\(bundle) (\(version); OS \(os))" + ) as CFMutableString let transform = NSString(string: "Any-Latin; Latin-ASCII; [:^ASCII:] Remove") as CFString if CFStringTransform(mutableUserAgent, nil, transform, false) == true { return mutableUserAgent as String @@ -117,7 +122,7 @@ final public class RequestInterceptor: Alamofire.RequestInterceptor { let url = config.baseURL.appendingPathComponent("/oauth2/access_token") let parameters: [String: Encodable & Sendable] = [ - "grant_type": Constants.GrantTypeRefreshToken, + "grant_type": AuthConstants.GrantTypeRefreshToken, "client_id": config.oAuthClientId, "refresh_token": refreshToken, "token_type": config.tokenType.rawValue, diff --git a/Core/Core/System/Protected.swift b/Core/Core/System/Protected.swift index 9c1c1e188..77f0e0997 100644 --- a/Core/Core/System/Protected.swift +++ b/Core/Core/System/Protected.swift @@ -5,7 +5,6 @@ // Created by Ivan Stepanok on 14.11.2024. // - import Foundation private protocol Lock: Sendable { diff --git a/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift b/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift index 15225b7e5..fda848184 100644 --- a/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift +++ b/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift @@ -18,27 +18,46 @@ struct AppReviewButton: View { } var body: some View { - Button(action: { - if isActive { action() } - }, label: { - Group { - HStack(spacing: 4) { - Text(type == .submit ? CoreLocalization.Review.Button.submit - : (type == .shareFeedback ? CoreLocalization.Review.Button.shareFeedback : CoreLocalization.Review.Button.rateUs )) - .foregroundColor(isActive ? Color.white : Color.black.opacity(0.6)) - .font(Theme.Fonts.labelLarge) - .padding(3) - - }.padding(.horizontal, 20) - .padding(.vertical, 9) - }.fixedSize() - .background(isActive + Button( + action: { + if isActive { action() } + }, + label: { + Group { + HStack(spacing: 4) { + Text( + type == .submit + ? CoreLocalization.Review.Button.submit + : ( + type == .shareFeedback + ? CoreLocalization.Review.Button.shareFeedback + : CoreLocalization.Review.Button.rateUs + ) + ) + .foregroundColor(isActive ? Color.white : Color.black.opacity(0.6)) + .font(Theme.Fonts.labelLarge) + .padding(3) + + }.padding(.horizontal, 20) + .padding(.vertical, 9) + }.fixedSize() + .background( + isActive ? Theme.Colors.accentColor - : Theme.Colors.cardViewStroke) - .accessibilityElement(children: .ignore) - .accessibilityLabel(type == .submit ? CoreLocalization.Review.Button.submit - : (type == .shareFeedback ? CoreLocalization.Review.Button.shareFeedback : CoreLocalization.Review.Button.rateUs )) - .cornerRadius(8) - }) + : Theme.Colors.cardViewStroke + ) + .accessibilityElement(children: .ignore) + .accessibilityLabel( + type == .submit + ? CoreLocalization.Review.Button.submit + : ( + type == .shareFeedback + ? CoreLocalization.Review.Button.shareFeedback + : CoreLocalization.Review.Button.rateUs + ) + ) + .cornerRadius(8) + } + ) } } diff --git a/Core/Core/View/Base/CourseCellView.swift b/Core/Core/View/Base/CourseCellView.swift index 37dada165..9fa667628 100644 --- a/Core/Core/View/Base/CourseCellView.swift +++ b/Core/Core/View/Base/CourseCellView.swift @@ -31,8 +31,14 @@ public struct CourseCellView: View { self.type = type self.courseImage = model.imageURL self.courseName = model.name - self.courseStart = model.courseStart?.dateToString(style: .startDDMonthYear, useRelativeDates: useRelativeDates) ?? "" - self.courseEnd = model.courseEnd?.dateToString(style: .endedMonthDay, useRelativeDates: useRelativeDates) ?? "" + self.courseStart = model.courseStart?.dateToString( + style: .startDDMonthYear, + useRelativeDates: useRelativeDates + ) ?? "" + self.courseEnd = model.courseEnd?.dateToString( + style: .endedMonthDay, + useRelativeDates: useRelativeDates + ) ?? "" self.courseOrg = model.org self.index = Double(index) + 1 self.cellsCount = cellsCount @@ -93,13 +99,19 @@ public struct CourseCellView: View { .padding(.vertical, type == .discovery ? 10 : 0) Spacer() } - + }.frame(height: 105) .background(Theme.Colors.background) .opacity(showView ? 1 : 0) .offset(y: showView ? 0 : 20) .accessibilityElement(children: .ignore) - .accessibilityLabel(courseName + " " + (type == .dashboard ? (courseEnd == "" ? courseStart : courseEnd) : "")) + .accessibilityLabel( + courseName + " " + ( + type == .dashboard + ? (courseEnd == "" ? courseStart : courseEnd) + : "" + ) + ) .onAppear { DispatchQueue.main.asyncAfter(deadline: .now()) { withAnimation(.easeInOut(duration: (index <= 5 ? 0.3 : 0.1)) @@ -108,7 +120,7 @@ public struct CourseCellView: View { } } } - + VStack { if Int(index) != cellsCount { Divider() @@ -157,7 +169,6 @@ struct CourseCellView_Previews: PreviewProvider { // Divider() } } - } } // swiftlint:enable all diff --git a/Core/Core/View/Base/FlexibleKeyboardInputView.swift b/Core/Core/View/Base/FlexibleKeyboardInputView.swift index ab4524405..52e0b45c7 100644 --- a/Core/Core/View/Base/FlexibleKeyboardInputView.swift +++ b/Core/Core/View/Base/FlexibleKeyboardInputView.swift @@ -48,7 +48,9 @@ public struct FlexibleKeyboardInputView: View { } ) .onPreferenceChange(ViewSizePreferenceKey.self) { size in - commentSize = size.height + Task { @MainActor in + commentSize = size.height + } } .overlay( TextEditor(text: $commentText) diff --git a/Core/Core/View/Base/SnackBarView.swift b/Core/Core/View/Base/SnackBarView.swift index fc61351de..9a0aad422 100644 --- a/Core/Core/View/Base/SnackBarView.swift +++ b/Core/Core/View/Base/SnackBarView.swift @@ -14,7 +14,7 @@ public struct SnackBarView: View { var action: (() -> Void)? private var safeArea: CGFloat { - UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0 + UIApplication.shared.oexKeyWindow?.safeAreaInsets.bottom ?? 0 } private let minHeight: CGFloat = 50 diff --git a/Core/Core/View/Base/Webview/WebView.swift b/Core/Core/View/Base/Webview/WebView.swift index 2d91fb652..cb3efaee4 100644 --- a/Core/Core/View/Base/Webview/WebView.swift +++ b/Core/Core/View/Base/Webview/WebView.swift @@ -161,7 +161,7 @@ public struct WebView: UIViewRepresentable { if url.absoluteString.starts(with: "file:///") { if url.pathExtension == "pdf" { - await parent.viewModel.openFile(url.absoluteString) + parent.viewModel.openFile(url.absoluteString) return .cancel } } diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index 90bc7338e..c07cbebd6 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -99,6 +99,12 @@ BAC0E0DE2B32F0F3006B68A9 /* DownloadsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAC0E0DD2B32F0F3006B68A9 /* DownloadsViewModel.swift */; }; BAD9CA2D2B2736BB00DE790A /* LessonLineProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA2C2B2736BB00DE790A /* LessonLineProgressView.swift */; }; BAD9CA4A2B2C88E000DE790A /* CourseVideoDownloadBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA492B2C88E000DE790A /* CourseVideoDownloadBarView.swift */; }; + CE6386642D07215E00C01D69 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6386632D07215E00C01D69 /* Line.swift */; }; + CE6386662D07217300C01D69 /* TimeLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6386652D07217300C01D69 /* TimeLineView.swift */; }; + CE6386682D07219B00C01D69 /* CourseDateListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6386672D07219B00C01D69 /* CourseDateListView.swift */; }; + CE63866A2D0721B600C01D69 /* CompletedBlocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6386692D0721B600C01D69 /* CompletedBlocks.swift */; }; + CE63866C2D0721D600C01D69 /* BlockStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE63866B2D0721D600C01D69 /* BlockStatusView.swift */; }; + CE63866E2D07220600C01D69 /* CourseDateStyleBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE63866D2D07220600C01D69 /* CourseDateStyleBlock.swift */; }; CE7CAF412CC1563500E0AC9D /* OEXFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CE7CAF402CC1563500E0AC9D /* OEXFoundation */; }; CEB1E2732CC14EC400921517 /* OEXFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CEB1E2722CC14EC400921517 /* OEXFoundation */; }; CEBCA4342CC13CDE00076589 /* YouTubePlayerKit in Frameworks */ = {isa = PBXBuildFile; productRef = CEBCA4332CC13CDE00076589 /* YouTubePlayerKit */; }; @@ -240,6 +246,12 @@ BAC0E0DD2B32F0F3006B68A9 /* DownloadsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsViewModel.swift; sourceTree = ""; }; BAD9CA2C2B2736BB00DE790A /* LessonLineProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonLineProgressView.swift; sourceTree = ""; }; BAD9CA492B2C88E000DE790A /* CourseVideoDownloadBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseVideoDownloadBarView.swift; sourceTree = ""; }; + CE6386632D07215E00C01D69 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; + CE6386652D07217300C01D69 /* TimeLineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeLineView.swift; sourceTree = ""; }; + CE6386672D07219B00C01D69 /* CourseDateListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDateListView.swift; sourceTree = ""; }; + CE6386692D0721B600C01D69 /* CompletedBlocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedBlocks.swift; sourceTree = ""; }; + CE63866B2D0721D600C01D69 /* BlockStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockStatusView.swift; sourceTree = ""; }; + CE63866D2D07220600C01D69 /* CourseDateStyleBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDateStyleBlock.swift; sourceTree = ""; }; DB205BFA2AE81B1200136EC2 /* CourseDateViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDateViewModelTests.swift; sourceTree = ""; }; DB7D6EAB2ADFCAC40036BB13 /* CourseDatesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDatesView.swift; sourceTree = ""; }; DB7D6EAD2ADFCB4A0036BB13 /* CourseDatesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDatesViewModel.swift; sourceTree = ""; }; @@ -653,6 +665,19 @@ path = Subviews; sourceTree = ""; }; + CE6386622D07215100C01D69 /* Elements */ = { + isa = PBXGroup; + children = ( + CE63866D2D07220600C01D69 /* CourseDateStyleBlock.swift */, + CE63866B2D0721D600C01D69 /* BlockStatusView.swift */, + CE6386692D0721B600C01D69 /* CompletedBlocks.swift */, + CE6386672D07219B00C01D69 /* CourseDateListView.swift */, + CE6386632D07215E00C01D69 /* Line.swift */, + CE6386652D07217300C01D69 /* TimeLineView.swift */, + ); + path = Elements; + sourceTree = ""; + }; D52670044E8768425E23C627 /* Pods */ = { isa = PBXGroup; children = ( @@ -686,6 +711,7 @@ DB7D6EAA2ADFCAA00036BB13 /* Dates */ = { isa = PBXGroup; children = ( + CE6386622D07215100C01D69 /* Elements */, DB7D6EAB2ADFCAC40036BB13 /* CourseDatesView.swift */, 97E7DF0E2B7C852A00A2A09B /* DatesStatusInfoView.swift */, DB7D6EAD2ADFCB4A0036BB13 /* CourseDatesViewModel.swift */, @@ -755,7 +781,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1410; - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1610; TargetAttributes = { 023812E3297AC8EA0087098F = { CreatedOnToolsVersion = 14.1; @@ -923,6 +949,7 @@ 02FF6FA72C20BFF800E44DD8 /* OfflineView.swift in Sources */, 0270210328E736E700F54332 /* CourseOutlineView.swift in Sources */, 068DDA602B1E198700FF8CCB /* CourseUnitVerticalsDropdownView.swift in Sources */, + CE6386662D07217300C01D69 /* TimeLineView.swift in Sources */, 067B7B512BED339200D1768F /* PipManagerProtocol.swift in Sources */, 022C64E029ADEA9B000F532B /* Data_UpdatesResponse.swift in Sources */, 02D4FC2E2BBD7C9C00C47748 /* MessageSectionView.swift in Sources */, @@ -930,6 +957,7 @@ 02454CA22A26190A0043052A /* EncodedVideoView.swift in Sources */, 065275352BB1B39C0093BCCA /* PlayerViewControllerHolder.swift in Sources */, 068DDA612B1E198700FF8CCB /* CourseUnitDropDownCell.swift in Sources */, + CE6386682D07219B00C01D69 /* CourseDateListView.swift in Sources */, BAC0E0DE2B32F0F3006B68A9 /* DownloadsViewModel.swift in Sources */, 02B6B3BC28E1D14F00232911 /* CourseRepository.swift in Sources */, 02280F60294B50030032823A /* CoursePersistenceProtocol.swift in Sources */, @@ -951,6 +979,7 @@ 073512E229C0E400005CFA41 /* BaseCourseViewModel.swift in Sources */, 0231124F28EDA811002588FB /* CourseUnitViewModel.swift in Sources */, 02F0144F28F46474002E513D /* CourseContainerView.swift in Sources */, + CE6386642D07215E00C01D69 /* Line.swift in Sources */, 02A8076829474831007F53AB /* CourseVerticalView.swift in Sources */, 97E7DF0F2B7C852A00A2A09B /* DatesStatusInfoView.swift in Sources */, 067B7B4F2BED339200D1768F /* PlayerDelegateProtocol.swift in Sources */, @@ -967,6 +996,8 @@ BAC0E0DB2B32F0AE006B68A9 /* CourseVideoDownloadBarViewModel.swift in Sources */, DB7D6EAE2ADFCB4A0036BB13 /* CourseDatesViewModel.swift in Sources */, 067B7B522BED339200D1768F /* SubtitlesView.swift in Sources */, + CE63866E2D07220600C01D69 /* CourseDateStyleBlock.swift in Sources */, + CE63866C2D0721D600C01D69 /* BlockStatusView.swift in Sources */, 02868AE52C19FE0B0003E339 /* DownloadActionView.swift in Sources */, 07DE59862BECB868001CBFBC /* CourseAnalytics.swift in Sources */, 02BB20182BFCE7B200364948 /* CustomDisclosureGroup.swift in Sources */, @@ -990,6 +1021,7 @@ 02E3803E2BFF9F0A00815AFA /* CourseProgressView.swift in Sources */, 022F8E162A1DFBC6008EFAB9 /* YouTubeVideoPlayerViewModel.swift in Sources */, 02B6B3BE28E1D15C00232911 /* CourseEndpoint.swift in Sources */, + CE63866A2D0721B600C01D69 /* CompletedBlocks.swift in Sources */, 02F71B4C2C1B200900FF936A /* DeviceStorageFullAlertView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1032,6 +1064,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1053,6 +1086,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1074,6 +1108,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1095,6 +1130,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1116,6 +1152,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1137,6 +1174,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1279,6 +1317,7 @@ baseConfigurationReference = ADC2A1B8183A674705F5F7E2 /* Pods-App-Course.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1286,6 +1325,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1296,6 +1337,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseDetails; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1314,6 +1356,7 @@ baseConfigurationReference = F4640D6B8065C404E0BC30D5 /* Pods-App-Course.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1321,6 +1364,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1331,6 +1376,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseDetails; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1412,6 +1458,7 @@ baseConfigurationReference = EDCFD7A1BFD2200B14F10F71 /* Pods-App-Course.debugdev.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1419,6 +1466,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1429,6 +1478,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseDetails; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1511,6 +1561,7 @@ baseConfigurationReference = E954A304FDC0409BEC34C125 /* Pods-App-Course.debugprod.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1518,6 +1569,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1528,6 +1581,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseDetails; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1604,6 +1658,7 @@ baseConfigurationReference = 4D2F08669DD2579EAAE02E2F /* Pods-App-Course.releasedev.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1611,6 +1666,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1621,6 +1678,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseDetails; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1696,6 +1754,7 @@ baseConfigurationReference = F496B47B12CFD866EA6845CC /* Pods-App-Course.releaseprod.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1703,6 +1762,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1713,6 +1774,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseDetails; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1794,6 +1856,7 @@ baseConfigurationReference = 3D506212980347A9D5A70E20 /* Pods-App-Course.debugstage.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1801,6 +1864,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1811,6 +1876,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseDetails; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1830,6 +1896,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1908,6 +1975,7 @@ baseConfigurationReference = A47C63D9EB0D866F303D4588 /* Pods-App-Course.releasestage.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -1915,6 +1983,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1925,6 +1995,7 @@ "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseDetails; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1943,6 +2014,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.4; diff --git a/Course/Course.xcodeproj/xcshareddata/xcschemes/Course.xcscheme b/Course/Course.xcodeproj/xcshareddata/xcschemes/Course.xcscheme index f6f5a5c64..d512b4343 100644 --- a/Course/Course.xcodeproj/xcshareddata/xcschemes/Course.xcscheme +++ b/Course/Course.xcodeproj/xcshareddata/xcschemes/Course.xcscheme @@ -1,6 +1,6 @@ Path { - var path = Path() - path.move(to: CGPoint(x: rect.midX, y: rect.minY)) - path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY)) - return path - } -} - -struct TimeLineView: View { - let status: CompletionStatus - - var body: some View { - ZStack(alignment: .top) { - VStack { - Line() - .stroke(style: StrokeStyle(lineWidth: 8, lineCap: .round, lineJoin: .round)) - .frame(maxHeight: .infinity, alignment: .top) - .padding(.top, 0) - .foregroundColor(status.foregroundColor) - } - } - .frame(width: 16) - } -} - -struct CourseDateListView: View { - @ObservedObject var viewModel: CourseDatesViewModel - @State private var isExpanded = false - @Binding var coordinate: CGFloat - @Binding var collapsed: Bool - @Binding var viewHeight: CGFloat - var courseDates: CourseDates - let courseID: String - - var body: some View { - GeometryReader { proxy in - VStack { - ScrollView { - DynamicOffsetView( - coordinate: $coordinate, - collapsed: $collapsed, - viewHeight: $viewHeight - ) - VStack(alignment: .leading, spacing: 0) { - - @State var status: SyncStatus = .offline - - CalendarSyncStatusView(status: status, router: viewModel.router) - .padding(.bottom, 16) - .task { - status = await viewModel.syncStatus() - } - - if !courseDates.hasEnded { - DatesStatusInfoView( - datesBannerInfo: courseDates.datesBannerInfo, - courseID: courseID, - courseDatesViewModel: viewModel, - screen: .courseDates - ) - .padding(.bottom, 16) - } - - ForEach(Array(viewModel.sortedStatuses), id: \.self) { status in - let courseDateBlockDict = courseDates.statusDatesBlocks[status]! - if status == .completed { - CompletedBlocks( - isExpanded: $isExpanded, - courseDateBlockDict: courseDateBlockDict, - viewModel: viewModel - ) - } else { - Text(status.rawValue) - .font(Theme.Fonts.titleSmall) - .padding(.top, 10) - .padding(.bottom, 10) - HStack { - TimeLineView(status: status) - .padding(.bottom, 15) - VStack(alignment: .leading) { - ForEach(courseDateBlockDict.keys.sorted(), id: \.self) { date in - let blocks = courseDateBlockDict[date]! - let block = blocks[0] - Text(block.formattedDate) - .font(Theme.Fonts.labelMedium) - .foregroundStyle(Theme.Colors.textPrimary) - BlockStatusView( - viewModel: viewModel, - block: block, - blocks: blocks - ) - } - } - } - } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - .padding(.vertical, 5) - .frameLimit(width: proxy.size.width) - Spacer(minLength: 200) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - } - } -} - -struct CompletedBlocks: View { - @Binding var isExpanded: Bool - let courseDateBlockDict: [Date: [CourseDateBlock]] - let viewModel: CourseDatesViewModel - - var body: some View { - VStack(alignment: .leading, spacing: 5) { - // Toggle button to expand/collapse the cell - Button(action: { - withAnimation { - isExpanded.toggle() - } - }) { - HStack { - VStack(alignment: .leading) { - Text(CompletionStatus.completed.localized) - .font(Theme.Fonts.titleSmall) - .foregroundColor(Theme.Colors.textPrimary) - - if !isExpanded { - let totalCount = courseDateBlockDict.values.reduce(0) { $0 + $1.count } - let itemsHidden = totalCount == 1 ? - CourseLocalization.CourseDates.itemHidden : - CourseLocalization.CourseDates.itemsHidden - Text("\(totalCount) \(itemsHidden)") - .font(Theme.Fonts.labelMedium) - .foregroundColor(Theme.Colors.textPrimary) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, 16) - .padding(.vertical, 8) - - Image(systemName: "chevron.down") - .labelStyle(.iconOnly) - .dropdownArrowRotationAnimation(value: isExpanded) - .foregroundColor(Theme.Colors.textPrimary) - .padding() - } - } - - // Your expandable content goes here - if isExpanded { - VStack(alignment: .leading) { - ForEach(courseDateBlockDict.keys.sorted(), id: \.self) { date in - let blocks = courseDateBlockDict[date]! - let block = blocks[0] - - Spacer() - Text(block.formattedDate) - .font(Theme.Fonts.labelMedium) - .foregroundStyle(Theme.Colors.textPrimary) - - ForEach(blocks) { block in - HStack(alignment: .top) { - block.blockImage?.swiftUIImage - .foregroundColor(Theme.Colors.textPrimary) - StyleBlock(block: block, viewModel: viewModel) - .padding(.bottom, 15) - Spacer() - if block.canShowLink && !block.firstComponentBlockID.isEmpty { - Image(systemName: "chevron.right") - .resizable() - .flipsForRightToLeftLayoutDirection(true) - - .scaledToFit() - .frame(width: 6.55, height: 11.15) - .labelStyle(.iconOnly) - .foregroundColor(Theme.Colors.textPrimary) - } - } - .padding(.trailing, 15) - } - } - } - .padding(.bottom, 15) - .padding(.leading, 16) - } - } - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(Theme.Colors.datesSectionStroke, lineWidth: 2) - ) - .background(Theme.Colors.datesSectionBackground) - } -} - -struct BlockStatusView: View { - let viewModel: CourseDatesViewModel - let block: CourseDateBlock - let blocks: [CourseDateBlock] - - var body: some View { - VStack(alignment: .leading) { - ForEach(blocks) { block in - HStack(alignment: .top) { - block.blockImage?.swiftUIImage - .foregroundColor(Theme.Colors.textPrimary) - StyleBlock(block: block, viewModel: viewModel) - .padding(.bottom, 15) - Spacer() - if block.canShowLink && !block.firstComponentBlockID.isEmpty { - Image(systemName: "chevron.right") - .resizable() - .flipsForRightToLeftLayoutDirection(true) - .scaledToFit() - .frame(width: 6.55, height: 11.15) - .labelStyle(.iconOnly) - .foregroundColor(Theme.Colors.textPrimary) - } - } - .padding(.trailing, 15) - } - .padding(.top, 0.2) - } - } - - func applyStyle(string: String, forgroundColor: Color, backgroundColor: Color) -> AttributedString { - var attributedString = AttributedString(string) - attributedString.font = Theme.Fonts.bodySmall - attributedString.foregroundColor = forgroundColor - attributedString.backgroundColor = backgroundColor - return attributedString - } -} - -struct StyleBlock: View { - let block: CourseDateBlock - let viewModel: CourseDatesViewModel - - var body: some View { - VStack(alignment: .leading) { - styleBlock(block: block) - if !block.description.isEmpty { - Text(block.description) - .font(Theme.Fonts.labelMedium) - .foregroundStyle(Theme.Colors.thisWeekTimelineColor) - .fixedSize(horizontal: false, vertical: true) - } - } - } - - func styleBlock(block: CourseDateBlock) -> some View { - var attributedString = AttributedString("") - - if let prefix = block.assignmentType, !prefix.isEmpty { - attributedString += AttributedString("\(prefix): ") - } - - attributedString += styleTitle(block: block) - - return Text(attributedString) - .font(Theme.Fonts.titleSmall) - .lineLimit(1) - .foregroundStyle({ - if block.isAssignment { - return block.isAvailable ? Theme.Colors.textPrimary : Color.gray.opacity(0.6) - } else { - return Theme.Colors.textPrimary - } - }()) - .onTapGesture { - if block.canShowLink && !block.firstComponentBlockID.isEmpty { - Task { - await viewModel.showCourseDetails( - componentID: block.firstComponentBlockID, - blockLink: block.link - ) - } - viewModel.logdateComponentTapped(block: block, supported: true) - } else { - viewModel.logdateComponentTapped(block: block, supported: false) - } - } - } - - func styleTitle(block: CourseDateBlock) -> AttributedString { - var attributedString = AttributedString(block.title) - attributedString.font = Theme.Fonts.titleSmall - return attributedString - } -} - fileprivate extension BlockStatus { var title: String { switch self { @@ -476,19 +183,6 @@ fileprivate extension BlockStatus { } } -fileprivate extension CompletionStatus { - var foregroundColor: Color { - switch self { - case .pastDue: return Theme.Colors.pastDueTimelineColor - case .today: return Theme.Colors.todayTimelineColor - case .thisWeek: return Theme.Colors.thisWeekTimelineColor - case .nextWeek: return Theme.Colors.nextWeekTimelineColor - case .upcoming: return Theme.Colors.upcomingTimelineColor - default: return Color.white.opacity(0) - } - } -} - fileprivate extension AttributedString { mutating func appendSpaces(_ count: Int = 1) { self += AttributedString(String(repeating: " ", count: count)) diff --git a/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift b/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift new file mode 100644 index 000000000..4e91d5914 --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift @@ -0,0 +1,41 @@ +// +// BlockStatusView.swift +// Course +// +// Created by Ivan Stepanok on 09.12.2024. +// + +import SwiftUI +import Core +import Theme + +struct BlockStatusView: View { + let viewModel: CourseDatesViewModel + let block: CourseDateBlock + let blocks: [CourseDateBlock] + + var body: some View { + VStack(alignment: .leading) { + ForEach(blocks) { block in + HStack(alignment: .top) { + block.blockImage?.swiftUIImage + .foregroundColor(Theme.Colors.textPrimary) + CourseDateStyleBlock(block: block, viewModel: viewModel) + .padding(.bottom, 15) + Spacer() + if block.canShowLink && !block.firstComponentBlockID.isEmpty { + Image(systemName: "chevron.right") + .resizable() + .flipsForRightToLeftLayoutDirection(true) + .scaledToFit() + .frame(width: 6.55, height: 11.15) + .labelStyle(.iconOnly) + .foregroundColor(Theme.Colors.textPrimary) + } + } + .padding(.trailing, 15) + } + .padding(.top, 0.2) + } + } +} diff --git a/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift b/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift new file mode 100644 index 000000000..6b3ae36da --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift @@ -0,0 +1,96 @@ +// +// CompletedBlocks.swift +// Course +// +// Created by Ivan Stepanok on 09.12.2024. +// + +import SwiftUI +import Core +import Theme + +struct CompletedBlocks: View { + @Binding var isExpanded: Bool + let courseDateBlockDict: [Date: [CourseDateBlock]] + let viewModel: CourseDatesViewModel + + var body: some View { + VStack(alignment: .leading, spacing: 5) { + // Toggle button to expand/collapse the cell + Button(action: { + withAnimation { + isExpanded.toggle() + } + }) { + HStack { + VStack(alignment: .leading) { + Text(CompletionStatus.completed.localized) + .font(Theme.Fonts.titleSmall) + .foregroundColor(Theme.Colors.textPrimary) + + if !isExpanded { + let totalCount = courseDateBlockDict.values.reduce(0) { $0 + $1.count } + let itemsHidden = totalCount == 1 ? + CourseLocalization.CourseDates.itemHidden : + CourseLocalization.CourseDates.itemsHidden + Text("\(totalCount) \(itemsHidden)") + .font(Theme.Fonts.labelMedium) + .foregroundColor(Theme.Colors.textPrimary) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 16) + .padding(.vertical, 8) + + Image(systemName: "chevron.down") + .labelStyle(.iconOnly) + .dropdownArrowRotationAnimation(value: isExpanded) + .foregroundColor(Theme.Colors.textPrimary) + .padding() + } + } + + // Your expandable content goes here + if isExpanded { + VStack(alignment: .leading) { + ForEach(courseDateBlockDict.keys.sorted(), id: \.self) { date in + let blocks = courseDateBlockDict[date]! + let block = blocks[0] + + Spacer() + Text(block.formattedDate) + .font(Theme.Fonts.labelMedium) + .foregroundStyle(Theme.Colors.textPrimary) + + ForEach(blocks) { block in + HStack(alignment: .top) { + block.blockImage?.swiftUIImage + .foregroundColor(Theme.Colors.textPrimary) + CourseDateStyleBlock(block: block, viewModel: viewModel) + .padding(.bottom, 15) + Spacer() + if block.canShowLink && !block.firstComponentBlockID.isEmpty { + Image(systemName: "chevron.right") + .resizable() + .flipsForRightToLeftLayoutDirection(true) + .scaledToFit() + .frame(width: 6.55, height: 11.15) + .labelStyle(.iconOnly) + .foregroundColor(Theme.Colors.textPrimary) + } + } + .padding(.trailing, 15) + } + } + } + .padding(.bottom, 15) + .padding(.leading, 16) + } + } + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Theme.Colors.datesSectionStroke, lineWidth: 2) + ) + .background(Theme.Colors.datesSectionBackground) + } +} diff --git a/Course/Course/Presentation/Dates/Elements/CourseDateListView.swift b/Course/Course/Presentation/Dates/Elements/CourseDateListView.swift new file mode 100644 index 000000000..d31c9a832 --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/CourseDateListView.swift @@ -0,0 +1,94 @@ +// +// CourseDateListView.swift +// Course +// +// Created by Ivan Stepanok on 09.12.2024. +// + +import SwiftUI +import Core +import Theme + +struct CourseDateListView: View { + @ObservedObject var viewModel: CourseDatesViewModel + @State private var isExpanded = false + @Binding var coordinate: CGFloat + @Binding var collapsed: Bool + @Binding var viewHeight: CGFloat + var courseDates: CourseDates + let courseID: String + + var body: some View { + GeometryReader { proxy in + VStack { + ScrollView { + DynamicOffsetView( + coordinate: $coordinate, + collapsed: $collapsed, + viewHeight: $viewHeight + ) + VStack(alignment: .leading, spacing: 0) { + + @State var status: SyncStatus = .offline + + CalendarSyncStatusView(status: status, router: viewModel.router) + .padding(.bottom, 16) + .task { + status = await viewModel.syncStatus() + } + + if !courseDates.hasEnded { + DatesStatusInfoView( + datesBannerInfo: courseDates.datesBannerInfo, + courseID: courseID, + courseDatesViewModel: viewModel, + screen: .courseDates + ) + .padding(.bottom, 16) + } + + ForEach(Array(viewModel.sortedStatuses), id: \.self) { status in + let courseDateBlockDict = courseDates.statusDatesBlocks[status]! + if status == .completed { + CompletedBlocks( + isExpanded: $isExpanded, + courseDateBlockDict: courseDateBlockDict, + viewModel: viewModel + ) + } else { + Text(status.rawValue) + .font(Theme.Fonts.titleSmall) + .padding(.top, 10) + .padding(.bottom, 10) + HStack { + TimeLineView(status: status) + .padding(.bottom, 15) + VStack(alignment: .leading) { + ForEach(courseDateBlockDict.keys.sorted(), id: \.self) { date in + let blocks = courseDateBlockDict[date]! + let block = blocks[0] + Text(block.formattedDate) + .font(Theme.Fonts.labelMedium) + .foregroundStyle(Theme.Colors.textPrimary) + BlockStatusView( + viewModel: viewModel, + block: block, + blocks: blocks + ) + } + } + } + } + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + .padding(.vertical, 5) + .frameLimit(width: proxy.size.width) + Spacer(minLength: 200) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + } +} diff --git a/Course/Course/Presentation/Dates/Elements/CourseDateStyleBlock.swift b/Course/Course/Presentation/Dates/Elements/CourseDateStyleBlock.swift new file mode 100644 index 000000000..630d062ec --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/CourseDateStyleBlock.swift @@ -0,0 +1,67 @@ +// +// CourseDateStyleBlock.swift +// Course +// +// Created by Ivan Stepanok on 09.12.2024. +// + +import SwiftUI +import Core +import Theme + +struct CourseDateStyleBlock: View { + let block: CourseDateBlock + let viewModel: CourseDatesViewModel + + var body: some View { + VStack(alignment: .leading) { + styleBlock(block: block) + if !block.description.isEmpty { + Text(block.description) + .font(Theme.Fonts.labelMedium) + .foregroundStyle(Theme.Colors.thisWeekTimelineColor) + .fixedSize(horizontal: false, vertical: true) + } + } + } + + func styleBlock(block: CourseDateBlock) -> some View { + var attributedString = AttributedString("") + + if let prefix = block.assignmentType, !prefix.isEmpty { + attributedString += AttributedString("\(prefix): ") + } + + attributedString += styleTitle(block: block) + + return Text(attributedString) + .font(Theme.Fonts.titleSmall) + .lineLimit(1) + .foregroundStyle({ + if block.isAssignment { + return block.isAvailable ? Theme.Colors.textPrimary : Color.gray.opacity(0.6) + } else { + return Theme.Colors.textPrimary + } + }()) + .onTapGesture { + if block.canShowLink && !block.firstComponentBlockID.isEmpty { + Task { + await viewModel.showCourseDetails( + componentID: block.firstComponentBlockID, + blockLink: block.link + ) + } + viewModel.logdateComponentTapped(block: block, supported: true) + } else { + viewModel.logdateComponentTapped(block: block, supported: false) + } + } + } + + func styleTitle(block: CourseDateBlock) -> AttributedString { + var attributedString = AttributedString(block.title) + attributedString.font = Theme.Fonts.titleSmall + return attributedString + } +} diff --git a/Course/Course/Presentation/Dates/Elements/Line.swift b/Course/Course/Presentation/Dates/Elements/Line.swift new file mode 100644 index 000000000..5c63d89a1 --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/Line.swift @@ -0,0 +1,17 @@ +// +// Line.swift +// Course +// +// Created by Ivan Stepanok on 09.12.2024. +// + +import SwiftUI + +struct Line: Shape { + func path(in rect: CGRect) -> Path { + var path = Path() + path.move(to: CGPoint(x: rect.midX, y: rect.minY)) + path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY)) + return path + } +} diff --git a/Course/Course/Presentation/Dates/Elements/TimeLineView.swift b/Course/Course/Presentation/Dates/Elements/TimeLineView.swift new file mode 100644 index 000000000..6e5f6ac3a --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/TimeLineView.swift @@ -0,0 +1,40 @@ +// +// TimeLineView.swift +// Course +// +// Created by Ivan Stepanok on 09.12.2024. +// + +import SwiftUI +import Core +import Theme + +struct TimeLineView: View { + let status: CompletionStatus + + var body: some View { + ZStack(alignment: .top) { + VStack { + Line() + .stroke(style: StrokeStyle(lineWidth: 8, lineCap: .round, lineJoin: .round)) + .frame(maxHeight: .infinity, alignment: .top) + .padding(.top, 0) + .foregroundColor(status.foregroundColor) + } + } + .frame(width: 16) + } +} + +fileprivate extension CompletionStatus { + var foregroundColor: Color { + switch self { + case .pastDue: return Theme.Colors.pastDueTimelineColor + case .today: return Theme.Colors.todayTimelineColor + case .thisWeek: return Theme.Colors.thisWeekTimelineColor + case .nextWeek: return Theme.Colors.nextWeekTimelineColor + case .upcoming: return Theme.Colors.upcomingTimelineColor + default: return Color.white.opacity(0) + } + } +} diff --git a/Course/Course/Presentation/Handouts/HandoutsViewModel.swift b/Course/Course/Presentation/Handouts/HandoutsViewModel.swift index 57f0f3c8e..c1756d9ed 100644 --- a/Course/Course/Presentation/Handouts/HandoutsViewModel.swift +++ b/Course/Course/Presentation/Handouts/HandoutsViewModel.swift @@ -53,7 +53,7 @@ public final class HandoutsViewModel: ObservableObject { self.handouts = handouts isShowProgress = false } - } catch let error { + } catch { isShowProgress = false } } @@ -63,7 +63,7 @@ public final class HandoutsViewModel: ObservableObject { do { updates = try await interactor.getUpdates(courseID: courseID) isShowProgress = false - } catch let error { + } catch { isShowProgress = false } } diff --git a/Course/Course/Presentation/Outline/CourseOutlineView.swift b/Course/Course/Presentation/Outline/CourseOutlineView.swift index 5061fc240..ec6eb502f 100644 --- a/Course/Course/Presentation/Outline/CourseOutlineView.swift +++ b/Course/Course/Presentation/Outline/CourseOutlineView.swift @@ -125,10 +125,14 @@ public struct CourseOutlineView: View { ) } else { if let courseStart = viewModel.courseStart { - Text(courseStart > Date() ? CourseLocalization.Outline.courseHasntStarted : "") - .frame(maxWidth: .infinity) - .frame(maxHeight: .infinity) - .padding(.top, 100) + Text( + courseStart > Date() + ? CourseLocalization.Outline.courseHasntStarted + : "" + ) + .frame(maxWidth: .infinity) + .frame(maxHeight: .infinity) + .padding(.top, 100) } Spacer(minLength: viewHeight < 200 ? 200 : viewHeight) } @@ -139,8 +143,8 @@ public struct CourseOutlineView: View { } .refreshable { Task { - await viewModel.getCourseBlocks(courseID: courseID, withProgress: false) - } + await viewModel.getCourseBlocks(courseID: courseID, withProgress: false) + } } .onRightSwipeGesture { viewModel.router.back() @@ -189,7 +193,7 @@ public struct CourseOutlineView: View { } .onAppear { Task { - await viewModel.updateCourseIfNeeded(courseID: courseID) + await viewModel.updateCourseIfNeeded(courseID: courseID) } } .background( @@ -293,7 +297,7 @@ public struct CourseOutlineView: View { content: { WebBrowser( url: url, - pageTitle: CourseLocalization.Outline.certificate, + pageTitle: CourseLocalization.Outline.certificate, connectivity: viewModel.connectivity ) } diff --git a/Course/Course/Presentation/Outline/CourseVertical/CourseVerticalImageView.swift b/Course/Course/Presentation/Outline/CourseVertical/CourseVerticalImageView.swift index 523c04e0a..329efc390 100644 --- a/Course/Course/Presentation/Outline/CourseVertical/CourseVerticalImageView.swift +++ b/Course/Course/Presentation/Outline/CourseVertical/CourseVerticalImageView.swift @@ -34,7 +34,7 @@ struct CourseVerticalImageView_Previews: PreviewProvider { id: "1", courseId: "123", topicId: "1", - graded: false, + graded: false, due: Date(), completion: 1, type: .video, diff --git a/Course/Course/Presentation/Subviews/CustomDisclosureGroup.swift b/Course/Course/Presentation/Subviews/CustomDisclosureGroup.swift index 2fbcb9046..753696baa 100644 --- a/Course/Course/Presentation/Subviews/CustomDisclosureGroup.swift +++ b/Course/Course/Presentation/Subviews/CustomDisclosureGroup.swift @@ -457,7 +457,7 @@ struct CustomDisclosureGroup_Previews: PreviewProvider { encodedVideo: "", displayName: "Course", childs: sampleCourseChapters, - media: DataLayer.CourseMedia.init(image: DataLayer.Image(raw: "", small: "", large: "")), + media: CourseMedia.init(image: CourseImage(raw: "", small: "", large: "")), certificate: nil, org: "org", isSelfPaced: false, diff --git a/Course/Course/Presentation/Unit/CourseUnitView.swift b/Course/Course/Presentation/Unit/CourseUnitView.swift index 2e239d5c4..2588d7369 100644 --- a/Course/Course/Presentation/Unit/CourseUnitView.swift +++ b/Course/Course/Presentation/Unit/CourseUnitView.swift @@ -318,8 +318,7 @@ public struct CourseUnitView: View { if !isHorizontal { Spacer(minLength: 150) } - } - else if let offlineURL = videoURLs[blockID] { + } else if let offlineURL = videoURLs[blockID] { EncodedVideoView( name: block.displayName, url: offlineURL, @@ -575,7 +574,6 @@ public struct CourseUnitView: View { } #if DEBUG -// swiftlint:disable all struct CourseUnitView_Previews: PreviewProvider { static var previews: some View { let blocks = [ @@ -642,7 +640,7 @@ struct CourseUnitView_Previews: PreviewProvider { encodedVideo: nil, multiDevice: false, offlineDownload: nil - ), + ) ] let chapters = [ @@ -731,5 +729,4 @@ struct CourseUnitView_Previews: PreviewProvider { )) } } -// swiftlint:enable all #endif diff --git a/Course/Course/Presentation/Unit/Subviews/DropdownList/CourseUnitDropDownList.swift b/Course/Course/Presentation/Unit/Subviews/DropdownList/CourseUnitDropDownList.swift index e449f77fc..d80a1f6a0 100644 --- a/Course/Course/Presentation/Unit/Subviews/DropdownList/CourseUnitDropDownList.swift +++ b/Course/Course/Presentation/Unit/Subviews/DropdownList/CourseUnitDropDownList.swift @@ -58,7 +58,7 @@ struct CourseUnitDropDownList_Previews: PreviewProvider { studentUrl: "", webUrl: "", encodedVideo: nil, - multiDevice: true, + multiDevice: true, offlineDownload: nil ), CourseBlock( diff --git a/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift b/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift index 39ccbd8b8..278faf66d 100644 --- a/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift +++ b/Course/CourseTests/Presentation/Container/CourseContainerViewModelTests.swift @@ -100,9 +100,13 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: childs, - media: DataLayer.CourseMedia(image: DataLayer.Image(raw: "", - small: "", - large: "")), + media: CourseMedia( + image: CourseImage( + raw: "", + small: "", + large: "" + ) + ), certificate: nil, org: "", isSelfPaced: true, @@ -169,9 +173,13 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [], - media: DataLayer.CourseMedia(image: DataLayer.Image(raw: "", - small: "", - large: "")), + media: CourseMedia( + image: CourseImage( + raw: "", + small: "", + large: "" + ) + ), certificate: nil, org: "", isSelfPaced: true, @@ -431,11 +439,13 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [chapter], - media: DataLayer.CourseMedia(image: DataLayer.Image( - raw: "", - small: "", - large: "" - )), + media: CourseMedia( + image: CourseImage( + raw: "", + small: "", + large: "" + ) + ), certificate: nil, org: "", isSelfPaced: true, @@ -573,11 +583,13 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [chapter], - media: DataLayer.CourseMedia(image: DataLayer.Image( - raw: "", - small: "", - large: "" - )), + media: CourseMedia( + image: CourseImage( + raw: "", + small: "", + large: "" + ) + ), certificate: nil, org: "", isSelfPaced: true, @@ -698,11 +710,13 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [chapter], - media: DataLayer.CourseMedia(image: DataLayer.Image( - raw: "", - small: "", - large: "" - )), + media: CourseMedia( + image: CourseImage( + raw: "", + small: "", + large: "" + ) + ), certificate: nil, org: "", isSelfPaced: true, @@ -824,11 +838,13 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [chapter], - media: DataLayer.CourseMedia(image: DataLayer.Image( - raw: "", - small: "", - large: "" - )), + media: CourseMedia( + image: CourseImage( + raw: "", + small: "", + large: "" + ) + ), certificate: nil, org: "", isSelfPaced: true, @@ -943,11 +959,9 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [chapter], - media: DataLayer.CourseMedia(image: DataLayer.Image( - raw: "", - small: "", - large: "" - )), + media: CourseMedia(image: CourseImage(raw: "", + small: "", + large: "")), certificate: nil, org: "", isSelfPaced: true, @@ -1078,11 +1092,9 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [chapter], - media: DataLayer.CourseMedia(image: DataLayer.Image( - raw: "", - small: "", - large: "" - )), + media: CourseMedia(image: CourseImage(raw: "", + small: "", + large: "")), certificate: nil, org: "", isSelfPaced: true, @@ -1236,11 +1248,13 @@ final class CourseContainerViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [chapter], - media: DataLayer.CourseMedia(image: DataLayer.Image( - raw: "", - small: "", - large: "" - )), + media: CourseMedia( + image: CourseImage( + raw: "", + small: "", + large: "" + ) + ), certificate: nil, org: "", isSelfPaced: true, diff --git a/Course/CourseTests/Presentation/Unit/CourseDateViewModelTests.swift b/Course/CourseTests/Presentation/Unit/CourseDateViewModelTests.swift index 1fb78183f..6e5579b61 100644 --- a/Course/CourseTests/Presentation/Unit/CourseDateViewModelTests.swift +++ b/Course/CourseTests/Presentation/Unit/CourseDateViewModelTests.swift @@ -42,9 +42,13 @@ final class CourseDateViewModelTests: XCTestCase { displayName: "", topicID: nil, childs: [], - media: DataLayer.CourseMedia(image: DataLayer.Image(raw: "", - small: "", - large: "")), + media: CourseMedia( + image: CourseImage( + raw: "", + small: "", + large: "" + ) + ), certificate: nil, org: "", isSelfPaced: true, diff --git a/Dashboard/Dashboard.xcodeproj/xcshareddata/xcschemes/Dashboard.xcscheme b/Dashboard/Dashboard.xcodeproj/xcshareddata/xcschemes/Dashboard.xcscheme index 4d44c4b3f..8ec46b7ad 100644 --- a/Dashboard/Dashboard.xcodeproj/xcshareddata/xcschemes/Dashboard.xcscheme +++ b/Dashboard/Dashboard.xcodeproj/xcshareddata/xcschemes/Dashboard.xcscheme @@ -1,6 +1,6 @@ UserComment { return try await repository.getResponse(responseID: responseID) } + //swiftlint:enable todo public func addCommentTo(threadID: String, rawBody: String, parentID: String? = nil) async throws -> Post { return try await repository.addCommentTo(threadID: threadID, diff --git a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift index 4722b958a..16e4def50 100644 --- a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift @@ -137,8 +137,7 @@ public class BaseResponsesViewModel { } func addNewPost(_ post: Post) { - var newPostWithAvatar = post - postComments?.comments.append(newPostWithAvatar) + postComments?.comments.append(post) itemsCount += 1 } diff --git a/Discussion/Discussion/Presentation/Comments/Base/CommentCell.swift b/Discussion/Discussion/Presentation/Comments/Base/CommentCell.swift index f4c56c102..d4970a894 100644 --- a/Discussion/Discussion/Presentation/Comments/Base/CommentCell.swift +++ b/Discussion/Discussion/Presentation/Comments/Base/CommentCell.swift @@ -184,16 +184,14 @@ struct CommentView_Previews: PreviewProvider { addCommentAvailable: true, useRelativeDates: true, leftLineEnabled: false, - onAvatarTap: { - _ in - }, + onAvatarTap: { _ in }, onLikeTap: {}, onReportTap: {}, onCommentsTap: {}, onFetchMore: {}) CommentCell( comment: comment, - addCommentAvailable: true, + addCommentAvailable: true, useRelativeDates: true, leftLineEnabled: false, onAvatarTap: {_ in}, @@ -209,7 +207,7 @@ struct CommentView_Previews: PreviewProvider { VStack(spacing: 0) { CommentCell( comment: comment, - addCommentAvailable: true, + addCommentAvailable: true, useRelativeDates: true, leftLineEnabled: false, onAvatarTap: {_ in}, @@ -219,7 +217,7 @@ struct CommentView_Previews: PreviewProvider { onFetchMore: {}) CommentCell( comment: comment, - addCommentAvailable: true, + addCommentAvailable: true, useRelativeDates: true, leftLineEnabled: false, onAvatarTap: {_ in}, diff --git a/Discussion/Discussion/Presentation/Comments/Base/ParentCommentView.swift b/Discussion/Discussion/Presentation/Comments/Base/ParentCommentView.swift index 3fce895d6..27c4f5229 100644 --- a/Discussion/Discussion/Presentation/Comments/Base/ParentCommentView.swift +++ b/Discussion/Discussion/Presentation/Comments/Base/ParentCommentView.swift @@ -172,7 +172,7 @@ struct ParentCommentView_Previews: PreviewProvider { return VStack { ParentCommentView( comments: comment, - isThread: true, + isThread: true, useRelativeDates: true, onAvatarTap: {_ in}, onLikeTap: {}, diff --git a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionSearchTopicsView.swift b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionSearchTopicsView.swift index e92727f89..aedba367d 100644 --- a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionSearchTopicsView.swift +++ b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionSearchTopicsView.swift @@ -196,7 +196,7 @@ struct DiscussionSearchTopicsView_Previews: PreviewProvider { static var previews: some View { let vm = DiscussionSearchTopicsViewModel( courseID: "123", - interactor: DiscussionInteractor.mock, + interactor: DiscussionInteractor.mock, storage: CoreStorageMock(), router: DiscussionRouterMock(), debounce: .searchDebounce diff --git a/Discussion/Discussion/Presentation/Posts/PostsView.swift b/Discussion/Discussion/Presentation/Posts/PostsView.swift index 71912528e..f3bfd7dc1 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsView.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsView.swift @@ -329,7 +329,7 @@ struct PostsView_Previews: PreviewProvider { let vm = PostsViewModel( interactor: DiscussionInteractor.mock, router: router, - config: ConfigMock(), + config: ConfigMock(), storage: CoreStorageMock() ) diff --git a/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme b/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme index cd0feecf1..9ed15e820 100644 --- a/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme +++ b/OpenEdX.xcodeproj/xcshareddata/xcschemes/OpenEdXDev.xcscheme @@ -1,6 +1,6 @@ + playerStateSubject: CurrentValueSubject ) in - let router: Router = r.resolve(Router.self)! - return YouTubeVideoPlayerViewModel( - languages: languages, - playerStateSubject: playerStateSubject, - connectivity: r.resolve(ConnectivityProtocol.self)!, - playerHolder: r.resolve( - YoutubePlayerViewControllerHolder.self, - arguments: url, - blockID, - courseID, - router.currentCourseTabSelection - )! - ) - }) + let router: Router = r.resolve(Router.self)! + return YouTubeVideoPlayerViewModel( + languages: languages, + playerStateSubject: playerStateSubject, + connectivity: r.resolve(ConnectivityProtocol.self)!, + playerHolder: r.resolve( + YoutubePlayerViewControllerHolder.self, + arguments: url, + blockID, + courseID, + router.currentCourseTabSelection + )! + ) + } + ) container.register( EncodedVideoPlayerViewModel.self, @@ -423,22 +424,23 @@ class ScreenAssembly: Assembly { languages: [SubtitleUrl], playerStateSubject: CurrentValueSubject ) in - let router: Router = r.resolve(Router.self)! - - let holder = r.resolve( - PlayerViewControllerHolder.self, - arguments: url, - blockID, - courseID, - router.currentCourseTabSelection - )! - return EncodedVideoPlayerViewModel( - languages: languages, - playerStateSubject: playerStateSubject, - connectivity: r.resolve(ConnectivityProtocol.self)!, - playerHolder: holder - ) - }) + let router: Router = r.resolve(Router.self)! + + let holder = r.resolve( + PlayerViewControllerHolder.self, + arguments: url, + blockID, + courseID, + router.currentCourseTabSelection + )! + return EncodedVideoPlayerViewModel( + languages: languages, + playerStateSubject: playerStateSubject, + connectivity: r.resolve(ConnectivityProtocol.self)!, + playerHolder: holder + ) + } + ) container.register(PlayerDelegateProtocol.self) { r in PlayerDelegate(pipManager: r.resolve(PipManagerProtocol.self)!) @@ -467,7 +469,7 @@ class ScreenAssembly: Assembly { appStorage: nil ) } - + container.register( PlayerViewControllerHolder.self, mainActorFactory: { (r, url: URL?, blockID: String, courseID: String, selectedCourseTab: Int) in @@ -623,4 +625,4 @@ class ScreenAssembly: Assembly { } } } -// swiftlint:enable function_body_length type_body_length +// swiftlint:enable function_body_length closure_parameter_position diff --git a/OpenEdX/Data/DashboardPersistence.swift b/OpenEdX/Data/DashboardPersistence.swift index 668c1beaa..b68ada0d8 100644 --- a/OpenEdX/Data/DashboardPersistence.swift +++ b/OpenEdX/Data/DashboardPersistence.swift @@ -150,7 +150,6 @@ public final class DashboardPersistence: DashboardPersistenceProtocol { } } - // swiftlint:disable function_body_length public func savePrimaryEnrollment(enrollments: PrimaryEnrollment) async { // Deleting all old data before saving new ones await clearOldEnrollmentsData() @@ -230,7 +229,6 @@ public final class DashboardPersistence: DashboardPersistenceProtocol { } } } - // swiftlint:enable function_body_length func clearOldEnrollmentsData() async { await container.performBackgroundTask { context in diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index 5e3ca3ab9..33b40bc3a 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -17,7 +17,7 @@ import WhatsNew import Swinject import OEXFoundation -// swiftlint:disable type_body_length file_length +// swiftlint:disable type_body_length class AnalyticsManager: AuthorizationAnalytics, MainScreenAnalytics, DiscoveryAnalytics, @@ -318,7 +318,7 @@ class AnalyticsManager: AuthorizationAnalytics, EventParamKey.name: EventBIValue.cookiePolicyClicked.rawValue, EventParamKey.category: EventCategory.profile ] - logEvent(.cookiePolicyClicked) + logEvent(.cookiePolicyClicked, parameters: parameters) } public func emailSupportClicked() { @@ -826,4 +826,4 @@ class AnalyticsManager: AuthorizationAnalytics, logEvent(.whatnewClose, parameters: parameters) } } -// swiftlint:enable type_body_length file_length +// swiftlint:enable type_body_length diff --git a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift index 8ac2cb107..39ee78e18 100644 --- a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift +++ b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift @@ -13,7 +13,7 @@ import Discussion import Course import Profile -// swiftlint:disable function_body_length type_body_length +// swiftlint:disable function_body_length //sourcery: AutoMockable @MainActor public protocol DeepLinkService { @@ -490,4 +490,4 @@ extension DeepLinkError: LocalizedError { } } } -// swiftlint:enable function_body_length type_body_length +// swiftlint:enable function_body_length diff --git a/OpenEdX/Managers/DeepLinkManager/DeepLinkRouter/DeepLinkRouter.swift b/OpenEdX/Managers/DeepLinkManager/DeepLinkRouter/DeepLinkRouter.swift index c77b2d4db..5601b3db7 100644 --- a/OpenEdX/Managers/DeepLinkManager/DeepLinkRouter/DeepLinkRouter.swift +++ b/OpenEdX/Managers/DeepLinkManager/DeepLinkRouter/DeepLinkRouter.swift @@ -118,10 +118,7 @@ extension Router: DeepLinkRouter { lastVisitedBlockID: nil ) } else { - showCourseDetais( - courseID: courseDetails.courseID, - title: courseDetails.courseTitle - ) + showCourseDetais(courseID: courseDetails.courseID, title: courseDetails.courseTitle) } } @@ -155,7 +152,6 @@ extension Router: DeepLinkRouter { default: break } - completion() } } diff --git a/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift b/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift index 1e4d37220..74144fd45 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift @@ -32,8 +32,6 @@ class BrazeListener: PushNotificationsListener { // segmentService.analytics?.receivedRemoteNotification(userInfo: userInfo) // } - - let link = PushLink(dictionary: dictionary) deepLinkManager.processLinkFromNotification(link) } diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index e238568dc..e0b742369 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -19,7 +19,7 @@ import Profile import WhatsNew import Combine -// swiftlint:disable file_length type_body_length +// swiftlint:disable type_body_length file_length public class Router: AuthorizationRouter, WhatsNewRouter, DiscoveryRouter, @@ -893,4 +893,4 @@ extension Router { navigationController.setViewControllers(viewControllers, animated: true) } } -// swiftlint:enable file_length type_body_length +// swiftlint:enable type_body_length file_length diff --git a/OpenEdX/View/MainScreenViewModel.swift b/OpenEdX/View/MainScreenViewModel.swift index 8cc55f59f..c6404138e 100644 --- a/OpenEdX/View/MainScreenViewModel.swift +++ b/OpenEdX/View/MainScreenViewModel.swift @@ -159,7 +159,7 @@ extension MainScreenViewModel { } do { - var coursesForSync = try await profileInteractor.enrollmentsStatus().filter { $0.recentlyActive } + let coursesForSync = try await profileInteractor.enrollmentsStatus().filter { $0.recentlyActive } let selectedCourses = await calendarManager.filterCoursesBySelected(fetchedCourses: coursesForSync) diff --git a/PrepareSarifToUpload.swift b/PrepareSarifToUpload.swift new file mode 100644 index 000000000..a58e36c8a --- /dev/null +++ b/PrepareSarifToUpload.swift @@ -0,0 +1,48 @@ +import Foundation + +let fileName = "swiftlint.report.sarif" +let errorLevel = "error" +let warningThresholdRuleId = "warning_threshold" + +let currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) +let fileURL = currentDirectoryURL.appendingPathComponent(fileName) + +do { + // Load the file data + let data = try Data(contentsOf: fileURL) + + // Decode JSON data + if var jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + var runs = jsonObject["runs"] as? [[String: Any]], + !runs.isEmpty { + + if var results = runs[0]["results"] as? [[String: Any]], !results.isEmpty { + // Filter out results based on the condition + results = results.filter { result in + let level = result["level"] as? String + let ruleId = result["ruleId"] as? String + // Keep results that don't match the deletion condition + return !(level == errorLevel && ruleId == warningThresholdRuleId) + } + + // Update the modified results array back into the runs structure + runs[0]["results"] = results + jsonObject["runs"] = runs + + // Encode the updated JSON data back to Data + let updatedData = try JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted]) + + // Write the updated data back to the file + try updatedData.write(to: fileURL) + print("Filtered JSON file updated successfully.") + } else { + print("No results found to filter.") + } + } else { + print("The JSON structure is not in the expected format.") + } +} catch { + print("Error reading or writing JSON file: \(error.localizedDescription)") +} + +exit(0) diff --git a/Profile/Profile.xcodeproj/xcshareddata/xcschemes/Profile.xcscheme b/Profile/Profile.xcodeproj/xcshareddata/xcschemes/Profile.xcscheme index ea25b5568..de4305320 100644 --- a/Profile/Profile.xcodeproj/xcshareddata/xcschemes/Profile.xcscheme +++ b/Profile/Profile.xcodeproj/xcshareddata/xcschemes/Profile.xcscheme @@ -1,6 +1,6 @@