From f316b1c724eb83f26e8dfa76fb6bc1d98e918d3e Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Mon, 9 Dec 2024 17:19:50 +0200 Subject: [PATCH 01/10] fix: resolve lint warnings --- .swiftlint.yml | 8 +- .../Presentation/Login/SignInView.swift | 20 +- .../Presentation/SSO/SSOWebView.swift | 11 +- Core/Core.xcodeproj/project.pbxproj | 2 +- .../Scroller/DismissKeyboardTapHandler.swift | 14 +- .../KeyboardAvoidingModifier.swift | 3 +- Core/Core/Configuration/CSSInjector.swift | 4 +- .../Config/DiscoveryConfig.swift | 4 +- Core/Core/Data/Model/Data_Media.swift | 12 + Core/Core/Domain/Model/CourseBlockModel.swift | 24 +- Core/Core/Extensions/DateExtension.swift | 7 +- Core/Core/Network/DownloadManager.swift | 19 +- Core/Core/Network/RequestInterceptor.swift | 13 +- Core/Core/System/Protected.swift | 1 - .../AppReview/Elements/AppReviewButton.swift | 32 +- Core/Core/View/Base/CourseCellView.swift | 18 +- Core/Core/View/Base/Webview/WebView.swift | 2 +- Course/Course.xcodeproj/project.pbxproj | 40 +++ Course/Course/Data/CourseRepository.swift | 4 +- .../Container/CourseContainerViewModel.swift | 2 + .../Presentation/Dates/CourseDatesView.swift | 306 ------------------ .../Dates/Elements/BlockStatusView.swift | 49 +++ .../Dates/Elements/CompletedBlocks.swift | 97 ++++++ .../Dates/Elements/CourseDateListView.swift | 94 ++++++ .../Presentation/Dates/Elements/Line.swift | 17 + .../Dates/Elements/StyleBlock.swift | 67 ++++ .../Dates/Elements/TimeLineView.swift | 40 +++ .../Handouts/HandoutsViewModel.swift | 4 +- .../Outline/CourseOutlineView.swift | 20 +- .../CourseVerticalImageView.swift | 2 +- .../Subviews/CustomDisclosureGroup.swift | 2 +- .../Presentation/Unit/CourseUnitView.swift | 9 +- .../DropdownList/CourseUnitDropDownList.swift | 2 +- .../Presentation/AllCoursesView.swift | 2 +- .../Elements/CourseCardView.swift | 4 +- .../Elements/PrimaryCardView.swift | 9 +- .../NativeDiscovery/SearchView.swift | 2 +- .../WebPrograms/ProgramWebviewView.swift | 4 +- .../Comments/Base/CommentCell.swift | 10 +- .../Comments/Base/ParentCommentView.swift | 2 +- .../DiscussionSearchTopicsView.swift | 2 +- .../Presentation/Posts/PostsView.swift | 2 +- OpenEdX/DI/AppAssembly.swift | 2 +- OpenEdX/DI/ContainerMainActor.swift | 2 + OpenEdX/DI/ScreenAssembly.swift | 76 ++--- OpenEdX/Data/DashboardPersistence.swift | 2 - OpenEdX/Info.plist | 15 + .../AnalyticsManager/AnalyticsManager.swift | 6 +- .../DeepLinkManager/DeepLinkManager.swift | 4 +- .../DeepLinkRouter/DeepLinkRouter.swift | 6 +- .../Listeners/BrazeListener.swift | 2 - OpenEdX/Router.swift | 4 +- OpenEdX/View/MainScreenViewModel.swift | 2 +- .../Data/Network/ProfileEndpoint.swift | 2 +- .../DatesAndCalendar/CalendarManager.swift | 6 +- .../DatesAndCalendar/CoursesToSyncView.swift | 14 +- .../DatesAndCalendarView.swift | 4 +- .../Elements/RelativeDatesToggleView.swift | 2 +- .../SyncCalendarOptionsView.swift | 2 +- .../EditProfile/EditProfileViewModel.swift | 2 - .../Subviews/ProfileSupportInfoView.swift | 2 +- .../Settings/VideoSettingsView.swift | 2 +- Theme/Theme/Theme.swift | 3 +- default_config/dev/config.yaml | 9 +- 64 files changed, 675 insertions(+), 480 deletions(-) create mode 100644 Course/Course/Presentation/Dates/Elements/BlockStatusView.swift create mode 100644 Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift create mode 100644 Course/Course/Presentation/Dates/Elements/CourseDateListView.swift create mode 100644 Course/Course/Presentation/Dates/Elements/Line.swift create mode 100644 Course/Course/Presentation/Dates/Elements/StyleBlock.swift create mode 100644 Course/Course/Presentation/Dates/Elements/TimeLineView.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 4ed67e1a6..c9ff6d2bb 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -31,6 +31,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 +49,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: 1200 + error: 1500 function_parameter_count: warning: 10 diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index ccf070b51..60bdffd48 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -123,7 +123,9 @@ public struct SignInView: View { HStack { if !viewModel.config.features.startupScreenEnabled { Button(CoreLocalization.SignIn.registerBtn) { - viewModel.router.showRegisterScreen(sourceScreen: viewModel.sourceScreen) + viewModel.router.showRegisterScreen( + sourceScreen: viewModel.sourceScreen + ) } .foregroundColor(Theme.Colors.accentColor) .accessibilityIdentifier("register_button") @@ -159,7 +161,7 @@ public struct SignInView: View { } } if viewModel.config.uiComponents.samlSSOLoginEnabled { - if !viewModel.config.uiComponents.loginRegistrationEnabled{ + if !viewModel.config.uiComponents.loginRegistrationEnabled { VStack(alignment: .center) { Text(AuthLocalization.SignIn.ssoHeading) .font(Theme.Fonts.headlineSmall) @@ -202,15 +204,21 @@ public struct SignInView: View { } else { let languageCode = Locale.current.language.languageCode?.identifier ?? "en" if viewModel.config.uiComponents.samlSSODefaultLoginButton { - StyledButton(viewModel.config.ssoButtonTitle[languageCode] as! String, action: { - viewModel.router.showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn) + StyledButton( + viewModel.config.ssoButtonTitle[languageCode] as! String, + action: { + viewModel.router + .showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn) }) .frame(maxWidth: .infinity) .padding(.top, 20) .accessibilityIdentifier("signin_SSO_button") } else { - StyledButton(viewModel.config.ssoButtonTitle[languageCode] as! String, action: { - viewModel.router.showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn) + StyledButton( + viewModel.config.ssoButtonTitle[languageCode] as! String, + action: { + viewModel.router + .showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn) }, color: .white, textColor: Theme.Colors.accentColor, 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..08e04a2b8 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -935,8 +935,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 */, diff --git a/Core/Core/AvoidingHelpers/Scroller/DismissKeyboardTapHandler.swift b/Core/Core/AvoidingHelpers/Scroller/DismissKeyboardTapHandler.swift index 7168eeded..a757893e1 100644 --- a/Core/Core/AvoidingHelpers/Scroller/DismissKeyboardTapHandler.swift +++ b/Core/Core/AvoidingHelpers/Scroller/DismissKeyboardTapHandler.swift @@ -16,13 +16,23 @@ final class DismissKeyboardTapHandler: NSObject { if isEnabled { let recognizer = makeTapGestureRecognizer() - UIApplication.shared.keyWindow?.addGestureRecognizer(recognizer) + UIApplication + .shared + .connectedScenes + .compactMap { ($0 as? UIWindowScene)?.keyWindow } + .last? + .addGestureRecognizer(recognizer) tapRecognizer = recognizer return } if let recognizer = tapRecognizer { - UIApplication.shared.keyWindow?.removeGestureRecognizer(recognizer) + UIApplication + .shared + .connectedScenes + .compactMap { ($0 as? UIWindowScene)?.keyWindow } + .last? + .removeGestureRecognizer(recognizer) tapRecognizer = nil } } diff --git a/Core/Core/AvoidingHelpers/ViewModifiers/KeyboardAvoidingModifier.swift b/Core/Core/AvoidingHelpers/ViewModifiers/KeyboardAvoidingModifier.swift index 325be190f..4a4879bb3 100644 --- a/Core/Core/AvoidingHelpers/ViewModifiers/KeyboardAvoidingModifier.swift +++ b/Core/Core/AvoidingHelpers/ViewModifiers/KeyboardAvoidingModifier.swift @@ -97,7 +97,8 @@ public extension View { /** Avoid keyboard with scrollable content - - parameter scrollerOptions: Specify scroller parameters such as distance between responder's bottom and keyboard top. + - parameter scrollerOptions: Specify scroller parameters such as distance + between responder's bottom and keyboard top. Create manually if you want to track scroll events - parameter partialAvoidingPadding: Height of content below avoiding area for partial keyboard avoiding - parameter dismissKeyboardByTap: Apply dismissal tap to the whole window diff --git a/Core/Core/Configuration/CSSInjector.swift b/Core/Core/Configuration/CSSInjector.swift index 7161965c5..8e4625c59 100644 --- a/Core/Core/Configuration/CSSInjector.swift +++ b/Core/Core/Configuration/CSSInjector.swift @@ -69,7 +69,7 @@ public class CSSInjector { } } - //swiftlint:disable function_body_length line_length + //swiftlint:disable line_length public func injectCSS( colorScheme: ColorScheme, html: String, @@ -149,7 +149,7 @@ public class CSSInjector { """ return style + replacedHTML } - //swiftlint:enable function_body_length line_length + //swiftlint:enable line_length } diff --git a/Core/Core/Configuration/Config/DiscoveryConfig.swift b/Core/Core/Configuration/Config/DiscoveryConfig.swift index a646658bb..90251d8ce 100644 --- a/Core/Core/Configuration/Config/DiscoveryConfig.swift +++ b/Core/Core/Configuration/Config/DiscoveryConfig.swift @@ -38,9 +38,7 @@ public class DiscoveryConfig: NSObject { public let type: DiscoveryConfigType public let webview: DiscoveryWebviewConfig public var isWebViewConfigured: Bool { - get { - return type == .webview && webview.baseURL != nil - } + return type == .webview && webview.baseURL != nil } init(dictionary: [String: AnyObject]) { diff --git a/Core/Core/Data/Model/Data_Media.swift b/Core/Core/Data/Model/Data_Media.swift index 8509573ac..8afef0ded 100644 --- a/Core/Core/Data/Model/Data_Media.swift +++ b/Core/Core/Data/Model/Data_Media.swift @@ -38,6 +38,18 @@ public extension DataLayer { } } +public extension DataLayer.CourseMedia { + var domain: CourseMedia { + return CourseMedia( + image: CourseImage( + raw: image.raw, + small: image.small, + large: image.large + ) + ) + } +} + public extension DataLayer { // MARK: - BannerImage struct BannerImage: Codable { diff --git a/Core/Core/Domain/Model/CourseBlockModel.swift b/Core/Core/Domain/Model/CourseBlockModel.swift index 91ff9801f..c9e24441b 100644 --- a/Core/Core/Domain/Model/CourseBlockModel.swift +++ b/Core/Core/Domain/Model/CourseBlockModel.swift @@ -20,7 +20,7 @@ public struct CourseStructure: Equatable, Sendable { public let displayName: String public let topicID: String? public var childs: [CourseChapter] - public let media: DataLayer.CourseMedia //FIXME Domain model + public let media: CourseMedia public let certificate: Certificate? public let org: String public let isSelfPaced: Bool @@ -35,7 +35,7 @@ public struct CourseStructure: Equatable, Sendable { displayName: String, topicID: String? = nil, childs: [CourseChapter], - media: DataLayer.CourseMedia, + media: CourseMedia, certificate: Certificate?, org: String, isSelfPaced: Bool, @@ -81,6 +81,26 @@ public struct CourseStructure: Equatable, Sendable { } } +public struct CourseMedia: Decodable, Sendable, Equatable { + public let image: CourseImage + + public init(image: CourseImage) { + self.image = image + } +} + +public struct CourseImage: Decodable, Sendable, Equatable { + public let raw: String + public let small: String + public let large: String + + public init(raw: String, small: String, large: String) { + self.raw = raw + self.small = small + self.large = large + } +} + public struct CourseProgress: Sendable { public let totalAssignmentsCount: Int? public let assignmentsCompleted: Int? diff --git a/Core/Core/Extensions/DateExtension.swift b/Core/Core/Extensions/DateExtension.swift index a4a49e462..7d2ddb9db 100644 --- a/Core/Core/Extensions/DateExtension.swift +++ b/Core/Core/Extensions/DateExtension.swift @@ -49,7 +49,6 @@ public extension Date { to: self ).day ?? 0 - // Calculate date ranges guard let sevenDaysAgo = calendar.date(byAdding: .day, value: -7, to: startOfCurrentDate), let sevenDaysAhead = calendar.date(byAdding: .day, value: 7, to: startOfCurrentDate) else { return dueInString + self.dateToString(style: .mmddyy, useRelativeDates: false) @@ -84,7 +83,11 @@ public extension Date { } if startOfSelfDate < startOfCurrentDate && startOfSelfDate >= 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) diff --git a/Core/Core/Network/DownloadManager.swift b/Core/Core/Network/DownloadManager.swift index 0c7b7ee8a..7bf031a9f 100644 --- a/Core/Core/Network/DownloadManager.swift +++ b/Core/Core/Network/DownloadManager.swift @@ -158,7 +158,8 @@ 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 = .init() private let backgroundTaskProvider = BackgroundTaskProvider() private var cancellables = Set() private nonisolated(unsafe) var failedDownloads: [DownloadDataTask] = [] @@ -467,9 +468,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 +494,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 +524,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 +551,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 @@ -831,7 +826,6 @@ public final class BackgroundTaskProvider: @unchecked Sendable { } // Mark - For testing and SwiftUI preview -// swiftlint:disable file_length #if DEBUG public class DownloadManagerMock: DownloadManagerProtocol { @@ -906,4 +900,3 @@ public class DownloadManagerMock: DownloadManagerProtocol { public func removeAppSupportDirectoryUnusedContent() {} } #endif -// swiftlint:enable file_length diff --git a/Core/Core/Network/RequestInterceptor.swift b/Core/Core/Network/RequestInterceptor.swift index 73fe5970c..155caa337 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 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..002d6379e 100644 --- a/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift +++ b/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift @@ -18,13 +18,21 @@ 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 )) + 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) @@ -36,8 +44,14 @@ struct AppReviewButton: View { ? 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 )) + .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..5f93307f8 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 @@ -99,7 +105,13 @@ public struct CourseCellView: View { .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)) 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..4af8a7479 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 /* StyleBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE63866D2D07220600C01D69 /* StyleBlock.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 /* StyleBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleBlock.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 /* StyleBlock.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 */, @@ -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 /* StyleBlock.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; @@ -1286,6 +1318,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1321,6 +1354,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1419,6 +1453,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1518,6 +1553,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1611,6 +1647,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1703,6 +1740,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1801,6 +1839,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1915,6 +1954,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/Course/Course/Data/CourseRepository.swift b/Course/Course/Data/CourseRepository.swift index 8545ecb0f..abf19ebe7 100644 --- a/Course/Course/Data/CourseRepository.swift +++ b/Course/Course/Data/CourseRepository.swift @@ -137,7 +137,7 @@ public actor CourseRepository: CourseRepositoryProtocol { displayName: courseBlock.displayName, topicID: courseBlock.userViewData?.topicID, childs: childs, - media: course.media, + media: course.media.domain, certificate: course.certificate?.domain, org: course.org ?? "", isSelfPaced: course.isSelfPaced, @@ -376,7 +376,7 @@ And there are various ways of describing it-- call it oral poetry or displayName: courseBlock.displayName, topicID: courseBlock.userViewData?.topicID, childs: childs, - media: course.media, + media: course.media.domain, certificate: course.certificate?.domain, org: course.org ?? "", isSelfPaced: course.isSelfPaced, diff --git a/Course/Course/Presentation/Container/CourseContainerViewModel.swift b/Course/Course/Presentation/Container/CourseContainerViewModel.swift index 54d7dca48..36c2303d8 100644 --- a/Course/Course/Presentation/Container/CourseContainerViewModel.swift +++ b/Course/Course/Presentation/Container/CourseContainerViewModel.swift @@ -59,6 +59,7 @@ extension CourseTab { } } +//swiftlint:disable type_body_length @MainActor public final class CourseContainerViewModel: BaseCourseViewModel { @@ -1162,3 +1163,4 @@ struct VerticalsDownloadState: Hashable { vertical.childs.filter { $0.isDownloadable && $0.type == .video } } } +//swiftlint:enable type_body_length diff --git a/Course/Course/Presentation/Dates/CourseDatesView.swift b/Course/Course/Presentation/Dates/CourseDatesView.swift index 0fd5f17b3..192b7c2a9 100644 --- a/Course/Course/Presentation/Dates/CourseDatesView.swift +++ b/Course/Course/Presentation/Dates/CourseDatesView.swift @@ -150,299 +150,6 @@ public struct CourseDatesView: View { } } -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 - } -} - -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..27510e43a --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift @@ -0,0 +1,49 @@ +// +// 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) + 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 + } +} diff --git a/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift b/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift new file mode 100644 index 000000000..3295dc224 --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift @@ -0,0 +1,97 @@ +// +// 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) + 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) + } +} 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/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/StyleBlock.swift b/Course/Course/Presentation/Dates/Elements/StyleBlock.swift new file mode 100644 index 000000000..3399ad740 --- /dev/null +++ b/Course/Course/Presentation/Dates/Elements/StyleBlock.swift @@ -0,0 +1,67 @@ +// +// StyleBlock.swift +// Course +// +// Created by Ivan Stepanok on 09.12.2024. +// + +import SwiftUI +import Core +import Theme + +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 + } +} 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..1fc3e6fdd 100644 --- a/Course/Course/Presentation/Unit/CourseUnitView.swift +++ b/Course/Course/Presentation/Unit/CourseUnitView.swift @@ -13,6 +13,7 @@ import Discussion import Combine import Theme +// swiftlint:disable type_body_length public struct CourseUnitView: View { @ObservedObject public var viewModel: CourseUnitViewModel @@ -318,8 +319,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 +575,6 @@ public struct CourseUnitView: View { } #if DEBUG -// swiftlint:disable all struct CourseUnitView_Previews: PreviewProvider { static var previews: some View { let blocks = [ @@ -642,7 +641,7 @@ struct CourseUnitView_Previews: PreviewProvider { encodedVideo: nil, multiDevice: false, offlineDownload: nil - ), + ) ] let chapters = [ @@ -731,5 +730,5 @@ struct CourseUnitView_Previews: PreviewProvider { )) } } -// swiftlint:enable all #endif +// swiftlint:enable type_body_length 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/Dashboard/Dashboard/Presentation/AllCoursesView.swift b/Dashboard/Dashboard/Presentation/AllCoursesView.swift index 0a5554ad1..4ab5fd905 100644 --- a/Dashboard/Dashboard/Presentation/AllCoursesView.swift +++ b/Dashboard/Dashboard/Presentation/AllCoursesView.swift @@ -206,7 +206,7 @@ struct AllCoursesView_Previews: PreviewProvider { let vm = AllCoursesViewModel( interactor: DashboardInteractor.mock, connectivity: Connectivity(), - analytics: DashboardAnalyticsMock(), + analytics: DashboardAnalyticsMock(), storage: CoreStorageMock() ) diff --git a/Dashboard/Dashboard/Presentation/Elements/CourseCardView.swift b/Dashboard/Dashboard/Presentation/Elements/CourseCardView.swift index e493c00d5..3c7b69b62 100644 --- a/Dashboard/Dashboard/Presentation/Elements/CourseCardView.swift +++ b/Dashboard/Dashboard/Presentation/Elements/CourseCardView.swift @@ -112,6 +112,7 @@ struct CourseCardView: View { } } +//swiftlint:disable line_length #if DEBUG #Preview { CourseCardView( @@ -122,8 +123,9 @@ struct CourseCardView: View { courseStartDate: nil, courseEndDate: Date(), hasAccess: true, - showProgress: true, + showProgress: true, useRelativeDates: true ).frame(width: 170) } #endif +//swiftlint:enable line_length diff --git a/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift b/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift index 8e96c9d77..be275e4ab 100644 --- a/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift +++ b/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift @@ -150,14 +150,9 @@ public struct PrimaryCardView: View { // futureAssignment if !futureAssignments.isEmpty { if futureAssignments.count == 1, let futureAssignment = futureAssignments.first { - let daysRemaining = Calendar.current.dateComponents( - [.day], - from: Date(), - to: futureAssignment.date - ).day ?? 0 courseButton( title: futureAssignment.title, - description: futureAssignment.date.dateToString( + description: futureAssignment.date.dateToString( style: .shortWeekdayMonthDayYear, useRelativeDates: useRelativeDates, dueIn: true @@ -296,6 +291,7 @@ public struct PrimaryCardView: View { } } +//swiftlint:disable line_length #if DEBUG struct PrimaryCardView_Previews: PreviewProvider { static var previews: some View { @@ -332,3 +328,4 @@ struct PrimaryCardView_Previews: PreviewProvider { } } #endif +//swiftlint:enable line_length diff --git a/Discovery/Discovery/Presentation/NativeDiscovery/SearchView.swift b/Discovery/Discovery/Presentation/NativeDiscovery/SearchView.swift index b2f8ef371..c059dbcba 100644 --- a/Discovery/Discovery/Presentation/NativeDiscovery/SearchView.swift +++ b/Discovery/Discovery/Presentation/NativeDiscovery/SearchView.swift @@ -224,7 +224,7 @@ struct SearchView_Previews: PreviewProvider { interactor: DiscoveryInteractor.mock, connectivity: Connectivity(), router: router, - analytics: DiscoveryAnalyticsMock(), + analytics: DiscoveryAnalyticsMock(), storage: CoreStorageMock(), debounce: .searchDebounce ) diff --git a/Discovery/Discovery/Presentation/WebPrograms/ProgramWebviewView.swift b/Discovery/Discovery/Presentation/WebPrograms/ProgramWebviewView.swift index 5f5078238..a206303d8 100644 --- a/Discovery/Discovery/Presentation/WebPrograms/ProgramWebviewView.swift +++ b/Discovery/Discovery/Presentation/WebPrograms/ProgramWebviewView.swift @@ -56,7 +56,7 @@ public struct ProgramWebviewView: View { WebView( viewModel: .init( url: URLString, - baseURL: "", + baseURL: "", openFile: {_ in}, injections: [.colorInversionCss] ), @@ -66,7 +66,7 @@ public struct ProgramWebviewView: View { force: true ) }, - navigationDelegate: viewModel, + navigationDelegate: viewModel, connectivity: viewModel.connectivity, webViewType: viewType.rawValue ) 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/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index b7b592ec1..7b934ba4d 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -175,7 +175,7 @@ class AppAssembly: Assembly { r.resolve(AppStorage.self)! }.inObjectScope(.container) - container.register(SSOHelper.self){ r in + container.register(SSOHelper.self) { r in SSOHelper( keychain: r.resolve(KeychainSwift.self)! ) diff --git a/OpenEdX/DI/ContainerMainActor.swift b/OpenEdX/DI/ContainerMainActor.swift index 294dbe9fd..0554d60c0 100644 --- a/OpenEdX/DI/ContainerMainActor.swift +++ b/OpenEdX/DI/ContainerMainActor.swift @@ -8,6 +8,7 @@ import Foundation @preconcurrency import Swinject +//swiftlint:disable line_length // MARK: - MainActor registration @available(iOS 13.0, macOS 10.15, *) extension Container { @@ -277,3 +278,4 @@ extension Container { } } +//swiftlint:enable line_length diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index e70aa8034..509fb257e 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -18,7 +18,7 @@ import Course import Discussion @preconcurrency import Combine -// swiftlint:disable function_body_length type_body_length +// swiftlint:disable function_body_length closure_parameter_position class ScreenAssembly: Assembly { func assemble(container: Container) { @@ -291,7 +291,7 @@ class ScreenAssembly: Assembly { ) } .inObjectScope(.weak) - + container.register(ManageAccountViewModel.self) { @MainActor r in ManageAccountViewModel( router: r.resolve(ProfileRouter.self)!, @@ -331,7 +331,7 @@ class ScreenAssembly: Assembly { // MARK: CourseScreensView container.register( CourseContainerViewModel.self - ) { @MainActor r, isActive, courseStart, courseEnd, enrollmentStart, enrollmentEnd, selection, lastVisitedBlockID in + ) { @MainActor r, isActive, courseStart, courseEnd, enrollStart, enrollEnd, selection, lastVisitedBlockID in CourseContainerViewModel( interactor: r.resolve(CourseInteractorProtocol.self)!, authInteractor: r.resolve(AuthInteractorProtocol.self)!, @@ -344,8 +344,8 @@ class ScreenAssembly: Assembly { isActive: isActive, courseStart: courseStart, courseEnd: courseEnd, - enrollmentStart: enrollmentStart, - enrollmentEnd: enrollmentEnd, + enrollmentStart: enrollStart, + enrollmentEnd: enrollEnd, lastVisitedBlockID: lastVisitedBlockID, coreAnalytics: r.resolve(CoreAnalytics.self)!, selection: selection @@ -395,22 +395,22 @@ class ScreenAssembly: Assembly { container.register( YouTubeVideoPlayerViewModel.self, mainActorFactory: { ( r, url: URL?, blockID: String, courseID: String, languages: [SubtitleUrl], - playerStateSubject: CurrentValueSubject + 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 +423,22 @@ 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 +467,7 @@ class ScreenAssembly: Assembly { appStorage: nil ) } - + container.register( PlayerViewControllerHolder.self, mainActorFactory: { (r, url: URL?, blockID: String, courseID: String, selectedCourseTab: Int) in @@ -623,4 +623,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/Info.plist b/OpenEdX/Info.plist index e9bd32e58..631e9050c 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -6,6 +6,21 @@ openEdx.offlineProgressSync + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLIconFile + + CFBundleURLName + test + CFBundleURLSchemes + + test + + + Configuration $(CONFIGURATION) FirebaseAppDelegateProxyEnabled 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..384b22d4b 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 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 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/Profile/Profile/Data/Network/ProfileEndpoint.swift b/Profile/Profile/Data/Network/ProfileEndpoint.swift index 6e99e9d3c..26fecea2e 100644 --- a/Profile/Profile/Data/Network/ProfileEndpoint.swift +++ b/Profile/Profile/Data/Network/ProfileEndpoint.swift @@ -94,7 +94,7 @@ enum ProfileEndpoint: EndPointType { "password": password ] return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) - case let .enrollmentsStatus(username): + case .enrollmentsStatus: return .requestParameters(parameters: nil, encoding: JSONEncoding.default) case .getCourseDates: return .requestParameters(encoding: JSONEncoding.default) diff --git a/Profile/Profile/Presentation/DatesAndCalendar/CalendarManager.swift b/Profile/Profile/Presentation/DatesAndCalendar/CalendarManager.swift index 047cf9511..fd29e33af 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/CalendarManager.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/CalendarManager.swift @@ -240,10 +240,6 @@ public final class CalendarManager: CalendarManagerProtocol { await removeOutdatedEvents(courseID: course.courseID) } - let newlyActiveCourses = fetchedCourses.filter { fetchedCourse in - courseCalendarStates.contains { $0.courseID == fetchedCourse.courseID } && fetchedCourse.recentlyActive - } - return fetchedCourses.filter { course in courseCalendarStates.contains { $0.courseID == course.courseID } && course.recentlyActive } @@ -319,7 +315,7 @@ public final class CalendarManager: CalendarManagerProtocol { startDate: startDate, endDate: endDate, secondAlert: secondAlert, - notes: notes, + notes: notes, location: courseName, calendar: calendar ) diff --git a/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift b/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift index b5d0aa1a2..8c4727954 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift @@ -89,10 +89,14 @@ public struct CoursesToSyncView: View { } else { ForEach( Array( - viewModel.coursesForSync.filter({ course in - course.synced == viewModel.synced && (!viewModel.hideInactiveCourses || course.recentlyActive) - }) - .sorted { $0.recentlyActive && !$1.recentlyActive } + viewModel.coursesForSync.filter( + { course in + course.synced == viewModel.synced && ( + !viewModel.hideInactiveCourses || course.recentlyActive + ) + }) + .sorted { $0.recentlyActive && !$1.recentlyActive + } .enumerated() ), id: \.offset @@ -153,7 +157,7 @@ struct CoursesToSyncView_Previews: PreviewProvider { interactor: ProfileInteractor(repository: ProfileRepositoryMock()), profileStorage: ProfileStorageMock(), persistence: ProfilePersistenceMock(), - calendarManager: CalendarManagerMock(), + calendarManager: CalendarManagerMock(), connectivity: Connectivity() ) return CoursesToSyncView(viewModel: vm) diff --git a/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarView.swift b/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarView.swift index 35ea4a7f7..52ef209f9 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarView.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/DatesAndCalendarView.swift @@ -185,9 +185,9 @@ struct DatesAndCalendarView_Previews: PreviewProvider { let vm = DatesAndCalendarViewModel( router: ProfileRouterMock(), interactor: ProfileInteractor(repository: ProfileRepositoryMock()), - profileStorage: ProfileStorageMock(), + profileStorage: ProfileStorageMock(), persistence: ProfilePersistenceMock(), - calendarManager: CalendarManagerMock(), + calendarManager: CalendarManagerMock(), connectivity: Connectivity() ) DatesAndCalendarView(viewModel: vm) diff --git a/Profile/Profile/Presentation/DatesAndCalendar/Elements/RelativeDatesToggleView.swift b/Profile/Profile/Presentation/DatesAndCalendar/Elements/RelativeDatesToggleView.swift index 967e52245..6a3c21a9f 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/Elements/RelativeDatesToggleView.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/Elements/RelativeDatesToggleView.swift @@ -25,7 +25,7 @@ struct RelativeDatesToggleView: View { .foregroundColor(Theme.Colors.textPrimary) } Text( - useRelativeDates + useRelativeDates ? ProfileLocalization.Options.showRelativeDates : ProfileLocalization.Options.showFullDates ) diff --git a/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift b/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift index 3942949be..b5aa1e691 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/SyncCalendarOptionsView.swift @@ -270,7 +270,7 @@ struct SyncCalendarOptionsView_Previews: PreviewProvider { interactor: ProfileInteractor(repository: ProfileRepositoryMock()), profileStorage: ProfileStorageMock(), persistence: ProfilePersistenceMock(), - calendarManager: CalendarManagerMock(), + calendarManager: CalendarManagerMock(), connectivity: Connectivity() ) SyncCalendarOptionsView(viewModel: vm) diff --git a/Profile/Profile/Presentation/EditProfile/EditProfileViewModel.swift b/Profile/Profile/Presentation/EditProfile/EditProfileViewModel.swift index 11bdbd4d0..6eac0c0bf 100644 --- a/Profile/Profile/Presentation/EditProfile/EditProfileViewModel.swift +++ b/Profile/Profile/Presentation/EditProfile/EditProfileViewModel.swift @@ -9,7 +9,6 @@ import Foundation import Core import SwiftUI -// swiftlint:disable type_body_length public struct Changes: Equatable, Sendable { public var shortBiography: String public var profileType: ProfileType @@ -373,4 +372,3 @@ public class EditProfileViewModel: ObservableObject { analytics.profileScreenEvent(.profileEdit, biValue: .profileEdit) } } -// swiftlint:enable type_body_length diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift index 3683465ab..986426d87 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift @@ -119,7 +119,7 @@ struct ProfileSupportInfoView: View { WebBrowser( url: viewModel.url.absoluteString, pageTitle: viewModel.title, - showProgress: true, + showProgress: true, connectivity: self.viewModel.connectivity ) diff --git a/Profile/Profile/Presentation/Settings/VideoSettingsView.swift b/Profile/Profile/Presentation/Settings/VideoSettingsView.swift index 61f9e626f..2400d1917 100644 --- a/Profile/Profile/Presentation/Settings/VideoSettingsView.swift +++ b/Profile/Profile/Presentation/Settings/VideoSettingsView.swift @@ -140,7 +140,7 @@ public struct VideoSettingsView: View { analytics: ProfileAnalyticsMock(), coreAnalytics: CoreAnalyticsMock(), config: ConfigMock(), - corePersistence: CorePersistenceMock(), + corePersistence: CorePersistenceMock(), connectivity: Connectivity() ) diff --git a/Theme/Theme/Theme.swift b/Theme/Theme/Theme.swift index dc8c1438e..221bb9ee2 100644 --- a/Theme/Theme/Theme.swift +++ b/Theme/Theme/Theme.swift @@ -178,7 +178,8 @@ public struct Theme: Sendable { nonisolated(unsafe) public private(set) static var textPrimary = ThemeAssets.textPrimary.color nonisolated(unsafe) public private(set) static var accentColor = ThemeAssets.accentColor.color nonisolated(unsafe) public private(set) static var accentXColor = ThemeAssets.accentXColor.color - nonisolated(unsafe) public private(set) static var navigationBarTintColor = ThemeAssets.navigationBarTintColor.color + nonisolated(unsafe) public private(set) static var navigationBarTintColor = + ThemeAssets.navigationBarTintColor.color public static func update( textPrimary: UIColor = ThemeAssets.textPrimary.color, diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml index ffd91e5dc..5277cfa3b 100644 --- a/default_config/dev/config.yaml +++ b/default_config/dev/config.yaml @@ -1,9 +1,12 @@ -API_HOST_URL: 'http://localhost:8000' +API_HOST_URL: 'https://axim-mobile.raccoongang.net' SSO_URL: 'http://localhost:8000' SSO_FINISHED_URL: 'http://localhost:8000' -ENVIRONMENT_DISPLAY_NAME: 'Localhost' +ENVIRONMENT_DISPLAY_NAME: 'lms-axim-stage' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' -OAUTH_CLIENT_ID: '' +OAUTH_CLIENT_ID: 'zP3vPz00c8fTRpYjNbVSlA1fxt9LnCxTM4JK1KQ0' + +DISCOVERY: + TYPE: "native" SSO_BUTTON_TITLE: ar: "الدخول عبر SSO" From d489e1cd6ed4603b1dd080660b8670cc2cd122a3 Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Wed, 11 Dec 2024 13:23:55 +0200 Subject: [PATCH 02/10] fix: resolve warnings and update swiftlint.yml --- .github/workflows/swiftlint.yml | 20 +++-- .swiftlint.yml | 10 +-- .../xcschemes/Authorization.xcscheme | 2 +- Core/Core.xcodeproj/project.pbxproj | 46 ++++++++-- .../xcshareddata/xcschemes/Core.xcscheme | 2 +- .../{Constants.swift => AuthConstants.swift} | 5 +- Core/Core/Configuration/Config/Config.swift | 2 +- Core/Core/Extensions/DateExtension.swift | 45 +++++----- Core/Core/Network/AuthEndpoint.swift | 2 +- Core/Core/Network/DownloadManager.swift | 77 ----------------- Core/Core/Network/DownloadManagerMock.swift | 85 +++++++++++++++++++ Core/Core/Network/RequestInterceptor.swift | 2 +- Core/Core/View/Base/SnackBarView.swift | 2 +- Course/Course.xcodeproj/project.pbxproj | 34 +++++++- .../xcshareddata/xcschemes/Course.xcscheme | 2 +- .../Container/CourseContainerViewModel.swift | 4 +- .../Presentation/Unit/CourseUnitView.swift | 2 - .../xcshareddata/xcschemes/Dashboard.xcscheme | 2 +- .../xcshareddata/xcschemes/Discovery.xcscheme | 2 +- .../xcschemes/Discussion.xcscheme | 2 +- .../Domain/DiscussionInteractor.swift | 2 + .../Base/BaseResponsesViewModel.swift | 2 +- .../xcschemes/OpenEdXDev.xcscheme | 2 +- .../xcschemes/OpenEdXProd.xcscheme | 2 +- .../xcschemes/OpenEdXStage.xcscheme | 2 +- OpenEdX/Router.swift | 4 +- PrepareSarifToUpload.swift | 48 +++++++++++ .../xcshareddata/xcschemes/Profile.xcscheme | 2 +- .../DatesAndCalendarViewModel.swift | 2 +- .../xcshareddata/xcschemes/Theme.xcscheme | 2 +- .../xcshareddata/xcschemes/WhatsNew.xcscheme | 2 +- 31 files changed, 270 insertions(+), 148 deletions(-) rename Core/Core/{Constants.swift => AuthConstants.swift} (70%) create mode 100644 Core/Core/Network/DownloadManagerMock.swift create mode 100644 PrepareSarifToUpload.swift diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml index d2d579ce3..9c69d060a 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 @@ -31,7 +28,20 @@ jobs: - name: Setup environment run: source ci_scripts/ci_prepare_env.sh && setup_github_actions_environment + xcodes select 16.1 - name: SwiftLint - run: - bundle exec fastlane linting + run: | + bundle exec fastlane linting -- --reporter sarif --output swiftlint.report.sarif + continue-on-error: true + + - 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 c9ff6d2bb..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 @@ -56,7 +57,7 @@ trailing_whitespace: ignores_empty_lines: true file_length: - warning: 1200 + warning: 850 error: 1500 function_parameter_count: @@ -72,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 @@ Bool { let calendar = Calendar.current guard let nextWeek = calendar.date(byAdding: .weekOfYear, value: 1, to: currentDate) else { return false } @@ -121,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 { @@ -153,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 } @@ -193,7 +193,7 @@ public extension Date { } let date = dateFormatter.string(from: self) - + switch style { case .courseStartsMonthDDYear: return CoreLocalization.Date.courseStarts + " " + date @@ -229,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 { @@ -253,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 7bf031a9f..c12347d10 100644 --- a/Core/Core/Network/DownloadManager.swift +++ b/Core/Core/Network/DownloadManager.swift @@ -437,7 +437,6 @@ public class DownloadManager: DownloadManagerProtocol { self.failedDownloads = [] } } - print(">>> IS NIL") return } if !connectivity.isInternetAvaliable { @@ -824,79 +823,3 @@ public final class BackgroundTaskProvider: @unchecked Sendable { } } } - -// 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/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 155caa337..c165bc4b0 100644 --- a/Core/Core/Network/RequestInterceptor.swift +++ b/Core/Core/Network/RequestInterceptor.swift @@ -122,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/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/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index 4af8a7479..ca106fe78 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -781,7 +781,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1410; - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1610; TargetAttributes = { 023812E3297AC8EA0087098F = { CreatedOnToolsVersion = 14.1; @@ -1064,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; @@ -1085,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; @@ -1106,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; @@ -1127,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; @@ -1148,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; @@ -1169,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; @@ -1311,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; @@ -1318,6 +1325,7 @@ 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 = ""; @@ -1329,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; @@ -1347,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; @@ -1354,6 +1364,7 @@ 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 = ""; @@ -1365,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; @@ -1446,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; @@ -1453,6 +1466,7 @@ 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 = ""; @@ -1464,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; @@ -1546,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; @@ -1553,6 +1569,7 @@ 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 = ""; @@ -1564,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; @@ -1640,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; @@ -1647,6 +1666,7 @@ 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 = ""; @@ -1658,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; @@ -1733,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; @@ -1740,6 +1762,7 @@ 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 = ""; @@ -1751,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; @@ -1832,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; @@ -1839,6 +1864,7 @@ 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 = ""; @@ -1850,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; @@ -1869,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; @@ -1947,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; @@ -1954,6 +1983,7 @@ 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 = ""; @@ -1965,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; @@ -1983,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 @@ 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..da3070355 100644 --- a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift @@ -137,7 +137,7 @@ public class BaseResponsesViewModel { } func addNewPost(_ post: Post) { - var newPostWithAvatar = post + let newPostWithAvatar = post postComments?.comments.append(newPostWithAvatar) itemsCount += 1 } 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 @@ Date: Wed, 11 Dec 2024 13:30:10 +0200 Subject: [PATCH 03/10] fix: update swiftlint --- .github/workflows/swiftlint.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml index 9c69d060a..49c831630 100644 --- a/.github/workflows/swiftlint.yml +++ b/.github/workflows/swiftlint.yml @@ -26,14 +26,17 @@ 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: Install pods + run: | + pod install + - name: SwiftLint run: | - bundle exec fastlane linting -- --reporter sarif --output swiftlint.report.sarif - continue-on-error: true + "${PODS_ROOT}/SwiftLint/swiftlint" --reporter sarif > swiftlint.report.sarif - name: Prepare swiftlint.report.sarif if: success() || failure() From d43620ffe350577ca3b86b955d0f26d86d274ad5 Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Wed, 11 Dec 2024 13:36:38 +0200 Subject: [PATCH 04/10] fix: update swiftlint --- .github/workflows/swiftlint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml index 49c831630..1a3178feb 100644 --- a/.github/workflows/swiftlint.yml +++ b/.github/workflows/swiftlint.yml @@ -36,6 +36,7 @@ jobs: - name: SwiftLint run: | + export PODS_ROOT=$(pwd)/Pods "${PODS_ROOT}/SwiftLint/swiftlint" --reporter sarif > swiftlint.report.sarif - name: Prepare swiftlint.report.sarif From e3c15664a96aaedae4a657fada9082d38db496cb Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Wed, 11 Dec 2024 13:57:27 +0200 Subject: [PATCH 05/10] fix: test lint warning --- Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift b/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift index be275e4ab..5b1f6f50a 100644 --- a/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift +++ b/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift @@ -291,7 +291,6 @@ public struct PrimaryCardView: View { } } -//swiftlint:disable line_length #if DEBUG struct PrimaryCardView_Previews: PreviewProvider { static var previews: some View { @@ -328,4 +327,3 @@ struct PrimaryCardView_Previews: PreviewProvider { } } #endif -//swiftlint:enable line_length From dda621e479476a9a311fb943534c79ae3cdcb11b Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Wed, 11 Dec 2024 14:03:36 +0200 Subject: [PATCH 06/10] Revert "fix: test lint warning" This reverts commit e3c15664a96aaedae4a657fada9082d38db496cb. --- Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift b/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift index 5b1f6f50a..be275e4ab 100644 --- a/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift +++ b/Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift @@ -291,6 +291,7 @@ public struct PrimaryCardView: View { } } +//swiftlint:disable line_length #if DEBUG struct PrimaryCardView_Previews: PreviewProvider { static var previews: some View { @@ -327,3 +328,4 @@ struct PrimaryCardView_Previews: PreviewProvider { } } #endif +//swiftlint:enable line_length From 01165381a6bb9be4bb09d2ad766f457feba5c6bd Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Wed, 11 Dec 2024 14:20:40 +0200 Subject: [PATCH 07/10] fix: update tests --- .../CourseContainerViewModelTests.swift | 96 +++++++++++-------- .../Unit/CourseDateViewModelTests.swift | 10 +- 2 files changed, 62 insertions(+), 44 deletions(-) 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, From fc9dedf7182fbeb79bf2a3570dea77b6618cd621 Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Wed, 11 Dec 2024 14:21:38 +0200 Subject: [PATCH 08/10] fix: update swiftlint --- .github/workflows/swiftlint.yml | 4 ---- OpenEdX/DI/ContainerMainActor.swift | 2 -- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml index 1a3178feb..15da04520 100644 --- a/.github/workflows/swiftlint.yml +++ b/.github/workflows/swiftlint.yml @@ -30,10 +30,6 @@ jobs: source ci_scripts/ci_prepare_env.sh && setup_github_actions_environment xcodes select 16.1 - - name: Install pods - run: | - pod install - - name: SwiftLint run: | export PODS_ROOT=$(pwd)/Pods diff --git a/OpenEdX/DI/ContainerMainActor.swift b/OpenEdX/DI/ContainerMainActor.swift index 0554d60c0..294dbe9fd 100644 --- a/OpenEdX/DI/ContainerMainActor.swift +++ b/OpenEdX/DI/ContainerMainActor.swift @@ -8,7 +8,6 @@ import Foundation @preconcurrency import Swinject -//swiftlint:disable line_length // MARK: - MainActor registration @available(iOS 13.0, macOS 10.15, *) extension Container { @@ -278,4 +277,3 @@ extension Container { } } -//swiftlint:enable line_length From a3a9dacf754feadea90cf988ea1ca01d55e0b739 Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Wed, 11 Dec 2024 15:46:51 +0200 Subject: [PATCH 09/10] fix: address feedback --- .../Scroller/DismissKeyboardTapHandler.swift | 8 +--- Core/Core/Network/DownloadManager.swift | 3 +- Core/Core/Network/RequestInterceptor.swift | 12 ++--- .../AppReview/Elements/AppReviewButton.swift | 47 ++++++++++--------- Core/Core/View/Base/CourseCellView.swift | 11 ++--- .../Dates/Elements/BlockStatusView.swift | 8 ---- .../Dates/Elements/CompletedBlocks.swift | 1 - .../Base/BaseResponsesViewModel.swift | 3 +- OpenEdX/DI/ContainerMainActor.swift | 3 +- OpenEdX/Info.plist | 15 ------ .../DatesAndCalendar/CoursesToSyncView.swift | 16 +++---- default_config/dev/config.yaml | 9 ++-- 12 files changed, 54 insertions(+), 82 deletions(-) diff --git a/Core/Core/AvoidingHelpers/Scroller/DismissKeyboardTapHandler.swift b/Core/Core/AvoidingHelpers/Scroller/DismissKeyboardTapHandler.swift index a757893e1..61cdfd1b4 100644 --- a/Core/Core/AvoidingHelpers/Scroller/DismissKeyboardTapHandler.swift +++ b/Core/Core/AvoidingHelpers/Scroller/DismissKeyboardTapHandler.swift @@ -18,9 +18,7 @@ final class DismissKeyboardTapHandler: NSObject { let recognizer = makeTapGestureRecognizer() UIApplication .shared - .connectedScenes - .compactMap { ($0 as? UIWindowScene)?.keyWindow } - .last? + .oexKeyWindow? .addGestureRecognizer(recognizer) tapRecognizer = recognizer return @@ -29,9 +27,7 @@ final class DismissKeyboardTapHandler: NSObject { if let recognizer = tapRecognizer { UIApplication .shared - .connectedScenes - .compactMap { ($0 as? UIWindowScene)?.keyWindow } - .last? + .oexKeyWindow? .removeGestureRecognizer(recognizer) tapRecognizer = nil } diff --git a/Core/Core/Network/DownloadManager.swift b/Core/Core/Network/DownloadManager.swift index c12347d10..47bef7d17 100644 --- a/Core/Core/Network/DownloadManager.swift +++ b/Core/Core/Network/DownloadManager.swift @@ -158,8 +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] = [] diff --git a/Core/Core/Network/RequestInterceptor.swift b/Core/Core/Network/RequestInterceptor.swift index c165bc4b0..d6bb9b8cd 100644 --- a/Core/Core/Network/RequestInterceptor.swift +++ b/Core/Core/Network/RequestInterceptor.swift @@ -43,12 +43,12 @@ 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 let mutableUserAgent = NSMutableString( string: "\(executable)/\(bundle) (\(version); OS \(os))" diff --git a/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift b/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift index 002d6379e..fda848184 100644 --- a/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift +++ b/Core/Core/View/Base/AppReview/Elements/AppReviewButton.swift @@ -26,33 +26,38 @@ struct AppReviewButton: View { Group { HStack(spacing: 4) { Text( - type == .submit ? CoreLocalization.Review.Button.submit + 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 + .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 5f93307f8..9fa667628 100644 --- a/Core/Core/View/Base/CourseCellView.swift +++ b/Core/Core/View/Base/CourseCellView.swift @@ -99,7 +99,7 @@ public struct CourseCellView: View { .padding(.vertical, type == .discovery ? 10 : 0) Spacer() } - + }.frame(height: 105) .background(Theme.Colors.background) .opacity(showView ? 1 : 0) @@ -107,9 +107,9 @@ public struct CourseCellView: View { .accessibilityElement(children: .ignore) .accessibilityLabel( courseName + " " + ( - type == .dashboard ? ( - courseEnd == "" ? courseStart : courseEnd - ) : "" + type == .dashboard + ? (courseEnd == "" ? courseStart : courseEnd) + : "" ) ) .onAppear { @@ -120,7 +120,7 @@ public struct CourseCellView: View { } } } - + VStack { if Int(index) != cellsCount { Divider() @@ -169,7 +169,6 @@ struct CourseCellView_Previews: PreviewProvider { // Divider() } } - } } // swiftlint:enable all diff --git a/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift b/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift index 27510e43a..8a422966e 100644 --- a/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift +++ b/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift @@ -38,12 +38,4 @@ struct BlockStatusView: View { .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 - } } diff --git a/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift b/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift index 3295dc224..81fef4b08 100644 --- a/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift +++ b/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift @@ -73,7 +73,6 @@ struct CompletedBlocks: View { Image(systemName: "chevron.right") .resizable() .flipsForRightToLeftLayoutDirection(true) - .scaledToFit() .frame(width: 6.55, height: 11.15) .labelStyle(.iconOnly) diff --git a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift index da3070355..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) { - let newPostWithAvatar = post - postComments?.comments.append(newPostWithAvatar) + postComments?.comments.append(post) itemsCount += 1 } diff --git a/OpenEdX/DI/ContainerMainActor.swift b/OpenEdX/DI/ContainerMainActor.swift index 294dbe9fd..4ae99ba37 100644 --- a/OpenEdX/DI/ContainerMainActor.swift +++ b/OpenEdX/DI/ContainerMainActor.swift @@ -8,6 +8,7 @@ import Foundation @preconcurrency import Swinject +//swiftlint:disable line_length // MARK: - MainActor registration @available(iOS 13.0, macOS 10.15, *) extension Container { @@ -275,5 +276,5 @@ extension Container { } } } - } +//swiftlint:enable line_length diff --git a/OpenEdX/Info.plist b/OpenEdX/Info.plist index 631e9050c..e9bd32e58 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -6,21 +6,6 @@ openEdx.offlineProgressSync - CFBundleURLTypes - - - CFBundleTypeRole - Viewer - CFBundleURLIconFile - - CFBundleURLName - test - CFBundleURLSchemes - - test - - - Configuration $(CONFIGURATION) FirebaseAppDelegateProxyEnabled diff --git a/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift b/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift index 8c4727954..ede274a85 100644 --- a/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift +++ b/Profile/Profile/Presentation/DatesAndCalendar/CoursesToSyncView.swift @@ -94,9 +94,9 @@ public struct CoursesToSyncView: View { course.synced == viewModel.synced && ( !viewModel.hideInactiveCourses || course.recentlyActive ) - }) - .sorted { $0.recentlyActive && !$1.recentlyActive - } + } + ) + .sorted { $0.recentlyActive && !$1.recentlyActive } .enumerated() ), id: \.offset @@ -140,11 +140,11 @@ public struct CoursesToSyncView: View { Text(ProfileLocalization.Sync.noSynced) .foregroundStyle(Theme.Colors.textPrimary) .font(Theme.Fonts.titleMedium) - Text(ProfileLocalization.Sync.noSyncedDescription) - .multilineTextAlignment(.center) - .foregroundStyle(Theme.Colors.textPrimary) - .font(Theme.Fonts.labelMedium) - .frame(width: 245) + Text(ProfileLocalization.Sync.noSyncedDescription) + .multilineTextAlignment(.center) + .foregroundStyle(Theme.Colors.textPrimary) + .font(Theme.Fonts.labelMedium) + .frame(width: 245) } } } diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml index 5277cfa3b..ffd91e5dc 100644 --- a/default_config/dev/config.yaml +++ b/default_config/dev/config.yaml @@ -1,12 +1,9 @@ -API_HOST_URL: 'https://axim-mobile.raccoongang.net' +API_HOST_URL: 'http://localhost:8000' SSO_URL: 'http://localhost:8000' SSO_FINISHED_URL: 'http://localhost:8000' -ENVIRONMENT_DISPLAY_NAME: 'lms-axim-stage' +ENVIRONMENT_DISPLAY_NAME: 'Localhost' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' -OAUTH_CLIENT_ID: 'zP3vPz00c8fTRpYjNbVSlA1fxt9LnCxTM4JK1KQ0' - -DISCOVERY: - TYPE: "native" +OAUTH_CLIENT_ID: '' SSO_BUTTON_TITLE: ar: "الدخول عبر SSO" From 0d4fdb8ba55028658ecf0e155efd620e2dda4ee1 Mon Sep 17 00:00:00 2001 From: IvanStepanok Date: Thu, 12 Dec 2024 13:53:09 +0200 Subject: [PATCH 10/10] fix: address feedback --- .../Presentation/Login/SignInView.swift | 29 +++++++++---------- .../Config/DiscoveryConfig.swift | 2 +- Core/Core/Data/Model/Data_Media.swift | 2 +- .../View/Base/FlexibleKeyboardInputView.swift | 4 ++- Course/Course.xcodeproj/project.pbxproj | 8 ++--- .../Dates/Elements/BlockStatusView.swift | 2 +- .../Dates/Elements/CompletedBlocks.swift | 2 +- ...Block.swift => CourseDateStyleBlock.swift} | 4 +-- OpenEdX/DI/ScreenAssembly.swift | 6 ++-- 9 files changed, 31 insertions(+), 28 deletions(-) rename Course/Course/Presentation/Dates/Elements/{StyleBlock.swift => CourseDateStyleBlock.swift} (96%) diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index 60bdffd48..dd1b895e5 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -171,7 +171,7 @@ public struct SignInView: View { .padding(.horizontal, 20) .accessibilityIdentifier("signin_sso_heading") } - + Divider() VStack(alignment: .center) { @@ -192,9 +192,8 @@ public struct SignInView: View { .accessibilityIdentifier("signin_sso_login_subtitle") } } - + VStack(alignment: .center) { - if viewModel.isShowProgress { HStack(alignment: .center) { ProgressBar(size: 40, lineWidth: 8) @@ -209,7 +208,8 @@ public struct SignInView: View { action: { viewModel.router .showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn) - }) + } + ) .frame(maxWidth: .infinity) .padding(.top, 20) .accessibilityIdentifier("signin_SSO_button") @@ -219,15 +219,14 @@ public struct SignInView: View { action: { viewModel.router .showSSOWebBrowser(title: CoreLocalization.SignIn.logInBtn) - }, - color: .white, - textColor: Theme.Colors.accentColor, - borderColor: Theme.Colors.accentColor) + }, + color: .white, + textColor: Theme.Colors.accentColor, + borderColor: Theme.Colors.accentColor) .frame(maxWidth: .infinity) .padding(.top, 20) .accessibilityIdentifier("signin_SSO_button") } - } } } @@ -266,7 +265,7 @@ public struct SignInView: View { .transition(.move(edge: .top)) .onAppear { doAfter(Theme.Timeout.snackbarMessageLongTimeout) { - viewModel.alertMessage = nil + viewModel.alertMessage = nil } } } @@ -279,7 +278,7 @@ public struct SignInView: View { }.transition(.move(edge: .bottom)) .onAppear { doAfter(Theme.Timeout.snackbarMessageLongTimeout) { - viewModel.errorMessage = nil + viewModel.errorMessage = nil } } } @@ -291,12 +290,12 @@ public struct SignInView: View { viewModel.trackScreenEvent() } } - + @ViewBuilder private var agreements: some View { if let eulaURL = viewModel.config.agreement.eulaURL, - let tosURL = viewModel.config.agreement.tosURL, - let policy = viewModel.config.agreement.privacyPolicyURL { + let tosURL = viewModel.config.agreement.tosURL, + let policy = viewModel.config.agreement.privacyPolicyURL { let text = AuthLocalization.SignIn.agreement( "\(viewModel.config.platformName)", eulaURL, @@ -314,7 +313,7 @@ public struct SignInView: View { .environment(\.openURL, OpenURLAction(handler: handleURL)) } } - + private func handleURL(_ url: URL) -> OpenURLAction.Result { viewModel.router.showWebBrowser(title: "", url: url) return .handled diff --git a/Core/Core/Configuration/Config/DiscoveryConfig.swift b/Core/Core/Configuration/Config/DiscoveryConfig.swift index 90251d8ce..1e8c9cb00 100644 --- a/Core/Core/Configuration/Config/DiscoveryConfig.swift +++ b/Core/Core/Configuration/Config/DiscoveryConfig.swift @@ -38,7 +38,7 @@ public class DiscoveryConfig: NSObject { public let type: DiscoveryConfigType public let webview: DiscoveryWebviewConfig public var isWebViewConfigured: Bool { - return type == .webview && webview.baseURL != nil + type == .webview && webview.baseURL != nil } init(dictionary: [String: AnyObject]) { diff --git a/Core/Core/Data/Model/Data_Media.swift b/Core/Core/Data/Model/Data_Media.swift index 8afef0ded..3e06532a9 100644 --- a/Core/Core/Data/Model/Data_Media.swift +++ b/Core/Core/Data/Model/Data_Media.swift @@ -40,7 +40,7 @@ public extension DataLayer { public extension DataLayer.CourseMedia { var domain: CourseMedia { - return CourseMedia( + CourseMedia( image: CourseImage( raw: image.raw, small: image.small, 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/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index ca106fe78..c07cbebd6 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -104,7 +104,7 @@ 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 /* StyleBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE63866D2D07220600C01D69 /* StyleBlock.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 */; }; @@ -251,7 +251,7 @@ 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 /* StyleBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleBlock.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 = ""; }; @@ -668,7 +668,7 @@ CE6386622D07215100C01D69 /* Elements */ = { isa = PBXGroup; children = ( - CE63866D2D07220600C01D69 /* StyleBlock.swift */, + CE63866D2D07220600C01D69 /* CourseDateStyleBlock.swift */, CE63866B2D0721D600C01D69 /* BlockStatusView.swift */, CE6386692D0721B600C01D69 /* CompletedBlocks.swift */, CE6386672D07219B00C01D69 /* CourseDateListView.swift */, @@ -996,7 +996,7 @@ BAC0E0DB2B32F0AE006B68A9 /* CourseVideoDownloadBarViewModel.swift in Sources */, DB7D6EAE2ADFCB4A0036BB13 /* CourseDatesViewModel.swift in Sources */, 067B7B522BED339200D1768F /* SubtitlesView.swift in Sources */, - CE63866E2D07220600C01D69 /* StyleBlock.swift in Sources */, + CE63866E2D07220600C01D69 /* CourseDateStyleBlock.swift in Sources */, CE63866C2D0721D600C01D69 /* BlockStatusView.swift in Sources */, 02868AE52C19FE0B0003E339 /* DownloadActionView.swift in Sources */, 07DE59862BECB868001CBFBC /* CourseAnalytics.swift in Sources */, diff --git a/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift b/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift index 8a422966e..4e91d5914 100644 --- a/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift +++ b/Course/Course/Presentation/Dates/Elements/BlockStatusView.swift @@ -20,7 +20,7 @@ struct BlockStatusView: View { HStack(alignment: .top) { block.blockImage?.swiftUIImage .foregroundColor(Theme.Colors.textPrimary) - StyleBlock(block: block, viewModel: viewModel) + CourseDateStyleBlock(block: block, viewModel: viewModel) .padding(.bottom, 15) Spacer() if block.canShowLink && !block.firstComponentBlockID.isEmpty { diff --git a/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift b/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift index 81fef4b08..6b3ae36da 100644 --- a/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift +++ b/Course/Course/Presentation/Dates/Elements/CompletedBlocks.swift @@ -66,7 +66,7 @@ struct CompletedBlocks: View { HStack(alignment: .top) { block.blockImage?.swiftUIImage .foregroundColor(Theme.Colors.textPrimary) - StyleBlock(block: block, viewModel: viewModel) + CourseDateStyleBlock(block: block, viewModel: viewModel) .padding(.bottom, 15) Spacer() if block.canShowLink && !block.firstComponentBlockID.isEmpty { diff --git a/Course/Course/Presentation/Dates/Elements/StyleBlock.swift b/Course/Course/Presentation/Dates/Elements/CourseDateStyleBlock.swift similarity index 96% rename from Course/Course/Presentation/Dates/Elements/StyleBlock.swift rename to Course/Course/Presentation/Dates/Elements/CourseDateStyleBlock.swift index 3399ad740..630d062ec 100644 --- a/Course/Course/Presentation/Dates/Elements/StyleBlock.swift +++ b/Course/Course/Presentation/Dates/Elements/CourseDateStyleBlock.swift @@ -1,5 +1,5 @@ // -// StyleBlock.swift +// CourseDateStyleBlock.swift // Course // // Created by Ivan Stepanok on 09.12.2024. @@ -9,7 +9,7 @@ import SwiftUI import Core import Theme -struct StyleBlock: View { +struct CourseDateStyleBlock: View { let block: CourseDateBlock let viewModel: CourseDatesViewModel diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index 509fb257e..91bff664e 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -410,7 +410,8 @@ class ScreenAssembly: Assembly { router.currentCourseTabSelection )! ) - }) + } + ) container.register( EncodedVideoPlayerViewModel.self, @@ -438,7 +439,8 @@ class ScreenAssembly: Assembly { connectivity: r.resolve(ConnectivityProtocol.self)!, playerHolder: holder ) - }) + } + ) container.register(PlayerDelegateProtocol.self) { r in PlayerDelegate(pipManager: r.resolve(PipManagerProtocol.self)!)