From d65637687880bd9ddb7b2f94c2936bf0ef2fba7d Mon Sep 17 00:00:00 2001 From: saeedbashir Date: Mon, 18 Dec 2023 18:07:29 +0500 Subject: [PATCH 01/14] feat: webview discovery implimentation --- Core/Core.xcodeproj/project.pbxproj | 15 ++ Core/Core/Configuration/Config/Config.swift | 1 + .../Config/DiscoveryConfig.swift | 57 +++++ .../Core/Extensions/RawStringExtactable.swift | 27 ++ Core/Core/SwiftGen/Strings.swift | 2 + Core/Core/View/Base/AlertView.swift | 19 +- .../View/Base/LogistrationBottomView.swift | 1 + Core/Core/View/Base/WebView.swift | 55 +++- Core/Core/en.lproj/Localizable.strings | 1 + Core/Core/uk.lproj/Localizable.strings | 1 + .../Details/CourseDetailsView.swift | 12 +- Discovery/Discovery.xcodeproj/project.pbxproj | 38 +++ Discovery/Discovery/Info.plist | 12 + .../Presentation/DiscoveryRouter.swift | 3 +- .../WebDiscovery/DiscoverWebviewModel.swift | 236 ++++++++++++++++++ .../WebDiscovery/DiscoveryURIDetails.swift | 31 +++ .../WebDiscovery/DiscoveryWebview.swift | 183 ++++++++++++++ .../WebDiscovery/URL+PathExtension.swift | 36 +++ Discovery/Discovery/SwiftGen/Strings.swift | 6 + .../Discovery/en.lproj/Localizable.strings | 3 + .../Discovery/uk.lproj/Localizable.strings | 3 + OpenEdX/DI/ScreenAssembly.swift | 12 + OpenEdX/Info.plist | 5 + OpenEdX/Router.swift | 51 +++- OpenEdX/View/MainScreenView.swift | 21 +- 25 files changed, 805 insertions(+), 26 deletions(-) create mode 100644 Core/Core/Configuration/Config/DiscoveryConfig.swift create mode 100644 Core/Core/Extensions/RawStringExtactable.swift create mode 100644 Discovery/Discovery/Info.plist create mode 100644 Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift create mode 100644 Discovery/Discovery/Presentation/WebDiscovery/DiscoveryURIDetails.swift create mode 100644 Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift create mode 100644 Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index e055bfc89..f93630d95 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -145,6 +145,11 @@ E055A53A2B18DC95008D9E5E /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E055A5382B18DC95008D9E5E /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E09179FD2B0F204E002AB695 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09179FC2B0F204D002AB695 /* ConfigTests.swift */; }; E0D586362B314CD3009B4BA7 /* LogistrationBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */; }; + E0D5861A2B2FF74C009B4BA7 /* DiscoveryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */; }; + E0D5861C2B2FF85B009B4BA7 /* RawStringExtactable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */; }; + E0D586362B314CD3009B4BA7 /* LogistrationBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */; }; + E0D5861A2B2FF74C009B4BA7 /* DiscoveryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */; }; + E0D5861C2B2FF85B009B4BA7 /* RawStringExtactable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -316,6 +321,11 @@ E055A5382B18DC95008D9E5E /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E09179FC2B0F204D002AB695 /* ConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogistrationBottomView.swift; sourceTree = ""; }; + E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryConfig.swift; sourceTree = ""; }; + E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawStringExtactable.swift; sourceTree = ""; }; + E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogistrationBottomView.swift; sourceTree = ""; }; + E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryConfig.swift; sourceTree = ""; }; + E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawStringExtactable.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -430,6 +440,7 @@ BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */, BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */, 02D400602B0678190029D168 /* SKStoreReviewControllerExtension.swift */, + E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */, ); path = Extensions; sourceTree = ""; @@ -710,6 +721,7 @@ BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */, BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */, A53A32342B233DEC005FE38A /* ThemeConfig.swift */, + E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */, ); path = Config; sourceTree = ""; @@ -996,6 +1008,7 @@ BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */, 0284DBFE28D48C5300830893 /* CourseItem.swift in Sources */, 0248C92329C075EF00DC8402 /* CourseBlockModel.swift in Sources */, + E0D5861A2B2FF74C009B4BA7 /* DiscoveryConfig.swift in Sources */, DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */, 072787B628D37A0E002E9142 /* Validator.swift in Sources */, 0236961D28F9A2D200EEF206 /* Data_AuthResponse.swift in Sources */, @@ -1003,6 +1016,8 @@ 0233D5712AF13EC800BAC8BD /* SelectMailClientView.swift in Sources */, BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */, 02B2B594295C5C7A00914876 /* Thread.swift in Sources */, + 027DB33528D8C8FE002B6862 /* Data_MyCourse.swift in Sources */, + E0D5861C2B2FF85B009B4BA7 /* RawStringExtactable.swift in Sources */, 027BD3BD2909478B00392132 /* UIView+EnclosingScrollView.swift in Sources */, BA8FA6682AD59A5700EA029A /* SocialAuthButton.swift in Sources */, 02D400612B0678190029D168 /* SKStoreReviewControllerExtension.swift in Sources */, diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 7cd10c224..7fcbe1c94 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -24,6 +24,7 @@ public protocol ConfigProtocol { var features: FeaturesConfig { get } var theme: ThemeConfig { get } var uiComponents: UIComponentsConfig { get } + var discovery: DiscoveryConfig { get } } public enum TokenType: String { diff --git a/Core/Core/Configuration/Config/DiscoveryConfig.swift b/Core/Core/Configuration/Config/DiscoveryConfig.swift new file mode 100644 index 000000000..592ba09ee --- /dev/null +++ b/Core/Core/Configuration/Config/DiscoveryConfig.swift @@ -0,0 +1,57 @@ +// +// DiscoveryConfig.swift +// Core +// +// Created by SaeedBashir on 12/18/23. +// + +import Foundation + +public enum DiscoveryConfigType: String { + case native + case webview + case none +} + +private enum DiscoveryKeys: String, RawStringExtractable { + case discoveryType = "TYPE" + case webview = "WEBVIEW" + case baseURL = "BASE_URL" + case courseDetailTemplate = "COURSE_DETAIL_TEMPLATE" + case programDetailTemplate = "PROGRAM_DETAIL_TEMPLATE" +} + +public class DiscoveryWebviewConfig: NSObject { + public let baseURL: String? + public let courseDetailTemplate: String? + public let programDetailTemplate: String? + + init(dictionary: [String: AnyObject]) { + baseURL = dictionary[DiscoveryKeys.baseURL] as? String + courseDetailTemplate = dictionary[DiscoveryKeys.courseDetailTemplate] as? String + programDetailTemplate = dictionary[DiscoveryKeys.programDetailTemplate] as? String + } +} + +public class DiscoveryConfig: NSObject { + public let type: DiscoveryConfigType + public let webview: DiscoveryWebviewConfig + + init(dictionary: [String: AnyObject]) { + type = (dictionary[DiscoveryKeys.discoveryType] as? String).flatMap { + DiscoveryConfigType(rawValue: $0) + } ?? .none + webview = DiscoveryWebviewConfig(dictionary: dictionary[DiscoveryKeys.webview] as? [String: AnyObject] ?? [:]) + } + + var isEnabled: Bool { + return type != .none + } +} + +private let key = "DISCOVERY" +extension Config { + public var discovery: DiscoveryConfig { + DiscoveryConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/Extensions/RawStringExtactable.swift b/Core/Core/Extensions/RawStringExtactable.swift new file mode 100644 index 000000000..8ab42f2ed --- /dev/null +++ b/Core/Core/Extensions/RawStringExtactable.swift @@ -0,0 +1,27 @@ +// +// RawStringExtactable.swift +// Core +// +// Created by SaeedBashir on 12/18/23. +// + +import Foundation + +public protocol RawStringExtractable { + var rawValue: String { get } +} + +public protocol DictionaryExtractionExtension { + associatedtype Key + associatedtype Value + subscript(key: Key) -> Value? { get } +} + +extension Dictionary: DictionaryExtractionExtension {} + +public extension DictionaryExtractionExtension where Self.Key == String { + + subscript(key :RawStringExtractable) -> Value? { + return self[key.rawValue] + } +} diff --git a/Core/Core/SwiftGen/Strings.swift b/Core/Core/SwiftGen/Strings.swift index 5fb863c94..71a71130f 100644 --- a/Core/Core/SwiftGen/Strings.swift +++ b/Core/Core/SwiftGen/Strings.swift @@ -193,6 +193,8 @@ public enum CoreLocalization { public enum Alert { /// Cancel public static let cancel = CoreLocalization.tr("Localizable", "WEBVIEW.ALERT.CANCEL", fallback: "Cancel") + /// Continue + public static let `continue` = CoreLocalization.tr("Localizable", "WEBVIEW.ALERT.CONTINUE", fallback: "Continue") /// Ok public static let ok = CoreLocalization.tr("Localizable", "WEBVIEW.ALERT.OK", fallback: "Ok") } diff --git a/Core/Core/View/Base/AlertView.swift b/Core/Core/View/Base/AlertView.swift index a9e3589d6..a578eefc7 100644 --- a/Core/Core/View/Base/AlertView.swift +++ b/Core/Core/View/Base/AlertView.swift @@ -17,7 +17,7 @@ public enum AlertViewType: Equatable { var contentPadding: CGFloat { switch self { case .`default`: - return 5 + return 16 case .action, .logOut, .leaveProfile: return 36 } @@ -113,6 +113,7 @@ public struct AlertView: View { Text(alertTitle) .font(Theme.Fonts.titleLarge) .padding(.horizontal, 40) + .padding(.top, 10) Text(alertMessage) .font(Theme.Fonts.bodyMedium) .multilineTextAlignment(.center) @@ -151,11 +152,15 @@ public struct AlertView: View { HStack { switch type { case let .`default`(positiveAction): - StyledButton(positiveAction, action: { okTapped() }) - .frame(maxWidth: 135) - StyledButton(CoreLocalization.Alert.cancel, action: { onCloseTapped() }) - .frame(maxWidth: 135) - .saturation(0) + HStack { + StyledButton(positiveAction, action: { okTapped() }) + .frame(maxWidth: 135) + StyledButton(CoreLocalization.Alert.cancel, action: { onCloseTapped() }) + .frame(maxWidth: 135) + .saturation(0) + } + .padding(.leading, 10) + .padding(.trailing, 10) case let .action(action, _): if !isHorizontal { VStack(spacing: 20) { @@ -274,7 +279,7 @@ public struct AlertView: View { }.padding(.trailing, isHorizontal ? 20 : 0) } } - .padding(.top, 5) + .padding(.top, 16) .padding(.bottom, isHorizontal ? 16 : type.contentPadding) } Button(action: { diff --git a/Core/Core/View/Base/LogistrationBottomView.swift b/Core/Core/View/Base/LogistrationBottomView.swift index 0bd1f27ef..f3412814e 100644 --- a/Core/Core/View/Base/LogistrationBottomView.swift +++ b/Core/Core/View/Base/LogistrationBottomView.swift @@ -14,6 +14,7 @@ public enum LogistrationSourceScreen: Equatable { case startup case discovery case courseDetail(String, String) + case programDetails(String) } public enum LogistrationAction { diff --git a/Core/Core/View/Base/WebView.swift b/Core/Core/View/Base/WebView.swift index 917fa9c7b..e0ff08e46 100644 --- a/Core/Core/View/Base/WebView.swift +++ b/Core/Core/View/Base/WebView.swift @@ -10,6 +10,12 @@ import WebKit import SwiftUI import Theme +public let WebviewReloadNotification = "webviewReloadNotification" + +public protocol WebViewNavigationDelegate: AnyObject { + func webView(_ webView: WKWebView, shouldLoad request: URLRequest, navigationAction: WKNavigationAction) -> Bool +} + public struct WebView: UIViewRepresentable { public class ViewModel: ObservableObject { @@ -25,12 +31,20 @@ public struct WebView: UIViewRepresentable { @ObservedObject var viewModel: ViewModel @Binding public var isLoading: Bool + weak var webViewNavDelegate: WebViewNavigationDelegate? + var refreshCookies: () async -> Void - public init(viewModel: ViewModel, isLoading: Binding, refreshCookies: @escaping () async -> Void) { + public init( + viewModel: ViewModel, + isLoading: Binding, + refreshCookies: @escaping () async -> Void, + navigationDelegate: WebViewNavigationDelegate? = nil + ) { self.viewModel = viewModel self._isLoading = isLoading self.refreshCookies = refreshCookies + self.webViewNavDelegate = navigationDelegate } public class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate { @@ -38,6 +52,9 @@ public struct WebView: UIViewRepresentable { init(_ parent: WebView) { self.parent = parent + super.init() + + addObserver() } public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { @@ -79,6 +96,16 @@ public struct WebView: UIViewRepresentable { guard let url = navigationAction.request.url else { return .cancel } + let isWebViewDelegateHandled = await ( + parent.webViewNavDelegate?.webView( + webView, + shouldLoad: navigationAction.request, navigationAction: navigationAction) ?? false + ) + + if isWebViewDelegateHandled { + return .cancel + } + let baseURL = await parent.viewModel.baseURL if !baseURL.isEmpty, !url.absoluteString.starts(with: baseURL) { if navigationAction.navigationType == .other { @@ -117,6 +144,26 @@ public struct WebView: UIViewRepresentable { } return .allow } + + private func addObserver() { + NotificationCenter.default.addObserver( + self, + selector: #selector(reload), + name: Notification.Name(WebviewReloadNotification), + object: nil + ) + } + + fileprivate var webview: WKWebView? + + @objc private func reload() { + parent.isLoading = true + webview?.reload() + } + + deinit { + NotificationCenter.default.removeObserver(self) + } } public func makeCoordinator() -> Coordinator { @@ -130,6 +177,8 @@ public struct WebView: UIViewRepresentable { webView.navigationDelegate = context.coordinator webView.uiDelegate = context.coordinator + context.coordinator.webview = webView + webView.scrollView.bounces = false webView.scrollView.alwaysBounceHorizontal = false webView.scrollView.showsHorizontalScrollIndicator = false @@ -139,8 +188,8 @@ public struct WebView: UIViewRepresentable { webView.backgroundColor = .clear webView.scrollView.backgroundColor = Theme.Colors.white.uiColor() webView.scrollView.alwaysBounceVertical = false - webView.scrollView.layer.cornerRadius = 24 - webView.scrollView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] +// webView.scrollView.layer.cornerRadius = 24 +// webView.scrollView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] webView.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 200, right: 0) return webView diff --git a/Core/Core/en.lproj/Localizable.strings b/Core/Core/en.lproj/Localizable.strings index 01ef3f850..673144473 100644 --- a/Core/Core/en.lproj/Localizable.strings +++ b/Core/Core/en.lproj/Localizable.strings @@ -70,6 +70,7 @@ "WEBVIEW.ALERT.OK" = "Ok"; "WEBVIEW.ALERT.CANCEL" = "Cancel"; +"WEBVIEW.ALERT.CONTINUE" = "Continue"; "REVIEW.VOTE_TITLE" = "Enjoying Open edX?"; "REVIEW.VOTE_DESCRIPTION" = "Your feedback matters to us. Would you take a moment to rate the app by tapping a star below? Thanks for your support!"; diff --git a/Core/Core/uk.lproj/Localizable.strings b/Core/Core/uk.lproj/Localizable.strings index d2128641b..ab592e519 100644 --- a/Core/Core/uk.lproj/Localizable.strings +++ b/Core/Core/uk.lproj/Localizable.strings @@ -69,6 +69,7 @@ "WEBVIEW.ALERT.OK" = "Так"; "WEBVIEW.ALERT.CANCEL" = "Скасувати"; +"WEBVIEW.ALERT.CONTINUE" = "Continue"; "REVIEW.VOTE_TITLE" = "Вам подобається Open edX?"; diff --git a/Course/Course/Presentation/Details/CourseDetailsView.swift b/Course/Course/Presentation/Details/CourseDetailsView.swift index 4be478f8e..d6aa06db8 100644 --- a/Course/Course/Presentation/Details/CourseDetailsView.swift +++ b/Course/Course/Presentation/Details/CourseDetailsView.swift @@ -221,7 +221,11 @@ private struct CourseStateView: View { case .enrollOpen: StyledButton(CourseLocalization.Details.enrollNow, action: { if !viewModel.userloggedIn { - viewModel.router.showRegisterScreen(sourceScreen: .courseDetail(courseDetails.courseID, courseDetails.courseTitle)) + viewModel.router.showRegisterScreen( + sourceScreen: .courseDetail( + courseDetails.courseID, + courseDetails.courseTitle) + ) } else { Task { await viewModel.enrollToCourse(id: courseDetails.courseID) @@ -238,7 +242,11 @@ private struct CourseStateView: View { case .alreadyEnrolled: StyledButton(CourseLocalization.Details.viewCourse, action: { if !viewModel.userloggedIn { - viewModel.router.showRegisterScreen(sourceScreen: .courseDetail(courseDetails.courseID, courseDetails.courseTitle)) + viewModel.router.showRegisterScreen( + sourceScreen: .courseDetail( + courseDetails.courseID, + courseDetails.courseTitle) + ) } else { viewModel.viewCourseClicked( courseId: courseDetails.courseID, diff --git a/Discovery/Discovery.xcodeproj/project.pbxproj b/Discovery/Discovery.xcodeproj/project.pbxproj index 6b3a59362..19cac5ced 100644 --- a/Discovery/Discovery.xcodeproj/project.pbxproj +++ b/Discovery/Discovery.xcodeproj/project.pbxproj @@ -32,6 +32,11 @@ CFC849452996A52A0055E497 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC849442996A52A0055E497 /* SearchViewModel.swift */; }; CFC8494C299A66080055E497 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = CFC8494E299A66080055E497 /* Localizable.stringsdict */; }; CFC84950299BE52C0055E497 /* SearchViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC8494F299BE52C0055E497 /* SearchViewModelTests.swift */; }; + E08D12E32B482D720096311A /* Course.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E08D12E22B482D720096311A /* Course.framework */; }; + E0D586202B300095009B4BA7 /* DiscoverWebviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */; }; + E0D586212B300095009B4BA7 /* DiscoveryWebview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */; }; + E0D586232B3000AD009B4BA7 /* DiscoveryURIDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586222B3000AD009B4BA7 /* DiscoveryURIDetails.swift */; }; + E0D586252B300134009B4BA7 /* URL+PathExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586242B300134009B4BA7 /* URL+PathExtension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -86,7 +91,13 @@ CFC849442996A52A0055E497 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; CFC8494D299A66080055E497 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; CFC8494F299BE52C0055E497 /* SearchViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModelTests.swift; sourceTree = ""; }; + E08D12E22B482D720096311A /* Course.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Course.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E0D586132B29F25A009B4BA7 /* Authorization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Authorization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverWebviewModel.swift; sourceTree = ""; }; + E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryWebview.swift; sourceTree = ""; }; + E0D586222B3000AD009B4BA7 /* DiscoveryURIDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryURIDetails.swift; sourceTree = ""; }; + E0D586242B300134009B4BA7 /* URL+PathExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+PathExtension.swift"; sourceTree = ""; }; + E0D586282B302C3A009B4BA7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; E192F9B4A7EECED9665AB8A7 /* Pods-App-Discovery.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Discovery/Pods-App-Discovery.releasedev.xcconfig"; sourceTree = ""; }; F340BD73D38B0DF9E4EA6482 /* Pods-App-Discovery-DiscoveryUnitTests.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery-DiscoveryUnitTests.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Discovery-DiscoveryUnitTests/Pods-App-Discovery-DiscoveryUnitTests.releaseprod.xcconfig"; sourceTree = ""; }; FF565519B9BBC73E92249648 /* Pods-App-Discovery-DiscoveryUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery-DiscoveryUnitTests.release.xcconfig"; path = "Target Support Files/Pods-App-Discovery-DiscoveryUnitTests/Pods-App-Discovery-DiscoveryUnitTests.release.xcconfig"; sourceTree = ""; }; @@ -106,6 +117,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E08D12E32B482D720096311A /* Course.framework in Frameworks */, 072787AD28D34D15002E9142 /* Core.framework in Frameworks */, 9F47BCC672941A9854404EC7 /* Pods_App_Discovery.framework in Frameworks */, ); @@ -179,6 +191,7 @@ 070019A228F6EF2700D5FC78 /* Presentation */ = { isa = PBXGroup; children = ( + E0D5861D2B300095009B4BA7 /* WebDiscovery */, 029242E52AE6976E00A940EC /* UpdateViews */, 072787B328D34D91002E9142 /* DiscoveryView.swift */, 0283347828D49A8700C828FC /* DiscoveryViewModel.swift */, @@ -214,6 +227,7 @@ 0727879B28D34C03002E9142 /* Discovery */ = { isa = PBXGroup; children = ( + E0D586282B302C3A009B4BA7 /* Info.plist */, 02EF39CB28D866C50058F6BD /* SwiftGen */, 0284DBF828D4831000830893 /* Data */, 0284DC0428D4996F00830893 /* Domain */, @@ -227,6 +241,7 @@ 072787AB28D34D15002E9142 /* Frameworks */ = { isa = PBXGroup; children = ( + E08D12E22B482D720096311A /* Course.framework */, E0D586132B29F25A009B4BA7 /* Authorization.framework */, 072787AC28D34D15002E9142 /* Core.framework */, 919E55130969D91EF03C4C0B /* Pods_App_Discovery.framework */, @@ -268,6 +283,17 @@ path = ../Pods; sourceTree = ""; }; + E0D5861D2B300095009B4BA7 /* WebDiscovery */ = { + isa = PBXGroup; + children = ( + E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */, + E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */, + E0D586222B3000AD009B4BA7 /* DiscoveryURIDetails.swift */, + E0D586242B300134009B4BA7 /* URL+PathExtension.swift */, + ); + path = WebDiscovery; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -483,15 +509,19 @@ 0284DBFC28D4856A00830893 /* DiscoveryEndpoint.swift in Sources */, 029737402949FB070051696B /* DiscoveryCoreModel.xcdatamodeld in Sources */, 029242E72AE6978400A940EC /* UpdateRequiredView.swift in Sources */, + E0D586232B3000AD009B4BA7 /* DiscoveryURIDetails.swift in Sources */, 0283347728D499BC00C828FC /* DiscoveryInteractor.swift in Sources */, 02F3BFDF29252F2F0051930C /* DiscoveryRouter.swift in Sources */, 0283347928D49A8700C828FC /* DiscoveryViewModel.swift in Sources */, 072787B428D34D91002E9142 /* DiscoveryView.swift in Sources */, + E0D586252B300134009B4BA7 /* URL+PathExtension.swift in Sources */, 029737422949FB3B0051696B /* DiscoveryPersistenceProtocol.swift in Sources */, 0284DC0328D4922900830893 /* DiscoveryRepository.swift in Sources */, 029242EB2AE6AB7B00A940EC /* UpdateNotificationView.swift in Sources */, 02EF39DC28D86BEF0058F6BD /* Strings.swift in Sources */, + E0D586202B300095009B4BA7 /* DiscoverWebviewModel.swift in Sources */, 02F1752F2A4DA3B60019CD70 /* DiscoveryAnalytics.swift in Sources */, + E0D586212B300095009B4BA7 /* DiscoveryWebview.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -731,6 +761,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Discovery/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -845,6 +876,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Discovery/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -1022,6 +1054,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Discovery/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -1057,6 +1090,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Discovery/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -1155,6 +1189,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Discovery/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -1254,6 +1289,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Discovery/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -1347,6 +1383,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Discovery/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -1439,6 +1476,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Discovery/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; diff --git a/Discovery/Discovery/Info.plist b/Discovery/Discovery/Info.plist new file mode 100644 index 000000000..f72a0f657 --- /dev/null +++ b/Discovery/Discovery/Info.plist @@ -0,0 +1,12 @@ + + + + + + diff --git a/Discovery/Discovery/Presentation/DiscoveryRouter.swift b/Discovery/Discovery/Presentation/DiscoveryRouter.swift index 32b13a6e4..cea10bb50 100644 --- a/Discovery/Discovery/Presentation/DiscoveryRouter.swift +++ b/Discovery/Discovery/Presentation/DiscoveryRouter.swift @@ -9,8 +9,8 @@ import Foundation import Core public protocol DiscoveryRouter: BaseRouter { - func showCourseDetais(courseID: String, title: String) + func showWebDiscoveryDetails(pathID: String, discoveryType: DiscoveryWebviewType, sourceScreen: LogistrationSourceScreen) func showUpdateRequiredView(showAccountLink: Bool) func showUpdateRecomendedView() func showDiscoverySearch(searchQuery: String?) @@ -23,6 +23,7 @@ public class DiscoveryRouterMock: BaseRouterMock, DiscoveryRouter { public override init() {} public func showCourseDetais(courseID: String, title: String) {} + public func showWebDiscoveryDetails(pathID: String, discoveryType: DiscoveryWebviewType, sourceScreen: LogistrationSourceScreen) {} public func showUpdateRequiredView(showAccountLink: Bool) {} public func showUpdateRecomendedView() {} public func showDiscoverySearch(searchQuery: String? = nil) {} diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift new file mode 100644 index 000000000..8267ead70 --- /dev/null +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift @@ -0,0 +1,236 @@ +// +// DiscoverWebviewModel.swift +// Discovery +// +// Created by SaeedBashir on 12/16/23. +// + +import Foundation +import Core +import WebKit +import SwiftUI +import Course +import Swinject + +public class DiscoveryWebviewModel: ObservableObject { + @Published var courseDetails: CourseDetails? + @Published private(set) var showProgress = false + @Published var showError: Bool = false + var errorMessage: String? { + didSet { + withAnimation { + showError = errorMessage != nil + } + } + } + + let router: DiscoveryRouter + let config: ConfigProtocol + let connectivity: ConnectivityProtocol + private let interactor: CourseInteractorProtocol + private let analytics: DiscoveryAnalytics + var request: URLRequest? + private let storage: CoreStorage + var sourceScreen: LogistrationSourceScreen + + var userloggedIn: Bool { + return !(storage.user?.username?.isEmpty ?? true) + } + + public init( + router: DiscoveryRouter, + config: ConfigProtocol, + interactor: CourseInteractorProtocol, + connectivity: ConnectivityProtocol, + analytics: DiscoveryAnalytics, + storage: CoreStorage, + sourceScreen: LogistrationSourceScreen = .default + ) { + self.router = router + self.config = config + self.interactor = interactor + self.connectivity = connectivity + self.analytics = analytics + self.storage = storage + self.sourceScreen = sourceScreen + } + + @MainActor + func getCourseDetail(courseID: String) async throws -> CourseDetails? { + return try await interactor.getCourseDetails(courseID: courseID) + } + + @MainActor + func enrollTo(courseID: String) async { + do { + guard userloggedIn else { + router.showRegisterScreen(sourceScreen: .discovery) + return + } + + showProgress = true + courseDetails = try await getCourseDetail(courseID: courseID) + + if courseDetails?.isEnrolled ?? false || courseState == .alreadyEnrolled { + showProgress = false + showCourseDetails() + return + } + + let courseAnalytics = Container.shared.resolve(CourseAnalytics.self) + + courseAnalytics?.courseEnrollClicked(courseId: courseID, courseName: courseDetails?.courseTitle ?? "") + _ = try await interactor.enrollToCourse(courseID: courseID) + courseAnalytics?.courseEnrollSuccess(courseId: courseID, courseName: courseDetails?.courseTitle ?? "") + courseDetails?.isEnrolled = true + showProgress = false + NotificationCenter.default.post(name: .onCourseEnrolled, object: courseID) + showCourseDetails() + } catch let error { + showProgress = false + if error.isInternetError || error is NoCachedDataError { + errorMessage = CoreLocalization.Error.slowOrNoInternetConnection + } else { + errorMessage = CoreLocalization.Error.unknownError + } + } + } + + private var courseState: CourseState? { + guard courseDetails?.isEnrolled == false else { return nil } + + if let enrollmentStart = courseDetails?.enrollmentStart, let enrollmentEnd = courseDetails?.enrollmentEnd { + let enrollmentsRange = DateInterval(start: enrollmentStart, end: enrollmentEnd) + if enrollmentsRange.contains(Date()) { + return .enrollOpen + } else { + return .enrollClose + } + } else { + return .enrollOpen + } + } +} + +extension DiscoveryWebviewModel: WebViewNavigationDelegate { + public func webView( + _ webView: WKWebView, + shouldLoad request: URLRequest, + navigationAction: WKNavigationAction + ) -> Bool { + guard let URL = request.url else { return false } + + if let urlAction = urlAction(from: URL), + handleNavigation(url: URL, urlAction: urlAction) { + return true + } + + let capturedLink = navigationAction.navigationType == .linkActivated + let outsideLink = (request.mainDocumentURL?.host != self.request?.url?.host) + var externalLink = false + + if let queryParameters = request.url?.queryParameters, + let externalLinkValue = queryParameters["external_link"] as? String, + externalLinkValue.caseInsensitiveCompare("true") == .orderedSame { + externalLink = true + } + + if let url = request.url, outsideLink || capturedLink || externalLink, UIApplication.shared.canOpenURL(url) { + DispatchQueue.main.async { [weak self] in + self?.router.presentAlert( + alertTitle: DiscoveryLocalization.Alert.leavingAppTitle, + alertMessage: DiscoveryLocalization.Alert.leavingAppMessage, + positiveAction: CoreLocalization.Webview.Alert.continue, + onCloseTapped: { + self?.router.dismiss(animated: true) + }, okTapped: { + UIApplication.shared.open(url, options: [:]) + }, type: .default(positiveAction: CoreLocalization.Webview.Alert.continue) + ) + } + return true + } + + return false + } + + private func urlAction(from url: URL) -> WebviewActions? { + guard url.isValidAppURLScheme, + let url = WebviewActions(rawValue: url.appURLHost) else { return nil } + return url + } + + private func handleNavigation(url: URL, urlAction: WebviewActions) -> Bool { + switch urlAction { + case .courseEnrollment: + if let urlData = parse(url: url), let courseID = urlData.courseId { + Task { + await enrollTo(courseID: courseID) + } + } + case .courseDetail: + guard let pathID = detailPathID(from: url) else { return false } + router.showWebDiscoveryDetails( + pathID: pathID, + discoveryType: .courseDetail(pathID), + sourceScreen: sourceScreen + ) + case .enrolledCourseDetail: + return showCourseDetails() + + case .programDetail: + guard let pathID = programDetailPathId(from: url) else { return false } + router.showWebDiscoveryDetails( + pathID: pathID, + discoveryType: .programDetail(pathID), + sourceScreen: sourceScreen + ) + + default: + break + } + + return true + } + + private func detailPathID(from url: URL) -> String? { + guard url.isValidAppURLScheme, + let path = url.queryParameters?[URLParameterKeys.pathId] as? String, + url.appURLHost == WebviewActions.courseDetail.rawValue else { return nil } + + return path + } + + private func parse(url: URL) -> (courseId: String?, emailOptIn: Bool)? { + guard url.isValidAppURLScheme else { return nil } + + let courseId = url.queryParameters?[URLParameterKeys.courseId] as? String + let emailOptIn = (url.queryParameters?[URLParameterKeys.emailOptIn] as? String).flatMap {Bool($0)} + + return (courseId, emailOptIn ?? false) + } + + private func programDetailPathId(from url: URL) -> String? { + guard url.isValidAppURLScheme, + let path = url.queryParameters?[URLParameterKeys.pathId] as? String, + url.appURLHost == WebviewActions.programDetail.rawValue else { return nil } + + return path + } + + @discardableResult private func showCourseDetails() -> Bool { + guard let courseRouter = Container.shared.resolve(CourseRouter.self), + let courseDetails = courseDetails else { return false } + courseRouter.showCourseScreens( + courseID: courseDetails.courseID, + isActive: nil, + courseStart: courseDetails.courseStart, + courseEnd: courseDetails.courseEnd, + enrollmentStart: courseDetails.enrollmentStart, + enrollmentEnd: courseDetails.enrollmentEnd, + title: courseDetails.courseTitle + ) + + return true + } +} diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryURIDetails.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryURIDetails.swift new file mode 100644 index 000000000..f4e6a6d94 --- /dev/null +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryURIDetails.swift @@ -0,0 +1,31 @@ +// +// DiscoveryURIDetails.swift +// Discovery +// +// Created by SaeedBashir on 12/18/23. +// + +import Foundation +import Core + +// Define your uri scheme +public enum URIString: String { + case appURLScheme = "edxapp" + case pathPlaceHolder = "{path_id}" +} + +public enum URLParameterKeys: String, RawStringExtractable { + case pathId = "path_id" + case courseId = "course_id" + case emailOptIn = "email_opt_in" +} + +// Define your hosts +public enum WebviewActions: String { + case courseEnrollment = "enroll" + case courseDetail = "course_info" + case enrolledCourseDetail = "enrolled_course_info" + case enrolledProgramDetail = "enrolled_program_info" + case programDetail = "program_info" + case courseProgram = "course" +} diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift new file mode 100644 index 000000000..c0b79bba8 --- /dev/null +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift @@ -0,0 +1,183 @@ +// +// DiscoveryWebview.swift +// Discovery +// +// Created by SaeedBashir on 12/16/23. +// + +import Foundation +import SwiftUI +import Theme +import Core + +public enum DiscoveryWebviewType: Equatable { + case discovery + case courseDetail(String) + case programDetail(String) +} + +public struct DiscoveryWebview: View { + @State private var searchQuery: String = "" + @State private var isLoading: Bool = true + + @ObservedObject private var viewModel: DiscoveryWebviewModel + private var router: DiscoveryRouter + private var discoveryType: DiscoveryWebviewType + private var pathID: String + + private var URLString: String { + switch discoveryType { + case .discovery: + if !searchQuery.isEmpty { + let baseURL = viewModel.config.discovery.webview.baseURL ?? "" + return buildQuery(baseURL: baseURL, params: ["q": searchQuery]) + } + + return viewModel.config.discovery.webview.baseURL ?? "" + case .courseDetail: + let template = viewModel.config.discovery.webview.courseDetailTemplate + return template?.replacingOccurrences( + of: URIString.pathPlaceHolder.rawValue, + with: pathID + ) ?? "" + + case .programDetail: + let template = viewModel.config.discovery.webview.programDetailTemplate + return template?.replacingOccurrences( + of: URIString.pathPlaceHolder.rawValue, + with: pathID + ) ?? "" + } + } + + private func buildQuery(baseURL: String, params: [String: String]) -> String { + var query = baseURL + for param in params { + let join = query.contains("?") ? "&" : "?" + let value = param.key + "=" + param.value + if !query.contains(find: value) { + query = query + join + value + } + } + + return query + } + + public init( + viewModel: DiscoveryWebviewModel, + router: DiscoveryRouter, + searchQuery: String? = nil, + discoveryType: DiscoveryWebviewType = .discovery, + pathID: String = "" + ) { + self.viewModel = viewModel + self.router = router + self._searchQuery = State(initialValue: searchQuery ?? "") + self.discoveryType = discoveryType + self.pathID = pathID + + if let url = URL(string: URLString) { + viewModel.request = URLRequest(url: url) + } + } + + public var body: some View { + GeometryReader { proxy in + VStack(alignment: .center) { + WebView( + viewModel: .init( + url: URLString, + baseURL: "" + ), + isLoading: $isLoading, + refreshCookies: {}, + navigationDelegate: viewModel + ) + + if isLoading || viewModel.showProgress { + HStack(alignment: .center) { + ProgressBar( + size: 40, + lineWidth: 8 + ) + .padding(.vertical, proxy.size.height / 2) + } + .frame(width: proxy.size.width, height: proxy.size.height) + } + + // MARK: - Show Error + if viewModel.showError { + VStack { + SnackBarView(message: viewModel.errorMessage) + } + .padding(.bottom, 20) + .transition(.move(edge: .bottom)) + .onAppear { + doAfter(Theme.Timeout.snackbarMessageLongTimeout) { + viewModel.errorMessage = nil + } + } + } + + if !viewModel.userloggedIn, !isLoading { + LogistrationBottomView { buttonAction in + switch buttonAction { + case .signIn: + viewModel.router.showLoginScreen(sourceScreen: sourceScreen) + case .register: + viewModel.router.showRegisterScreen(sourceScreen: sourceScreen) + } + } + } + } + + // MARK: - Offline mode SnackBar + OfflineSnackBarView( + connectivity: viewModel.connectivity, + reloadAction: { + NotificationCenter.default.post( + name: NSNotification.Name(rawValue: WebviewReloadNotification), + object: nil + ) + }) + } + .navigationBarHidden(viewModel.sourceScreen == .default && discoveryType == .discovery) + .navigationTitle(CoreLocalization.Mainscreen.discovery) + .background(Theme.Colors.background.ignoresSafeArea()) + .onFirstAppear { + if case let .courseDetail(pathID, _) = viewModel.sourceScreen { + viewModel.router.showWebDiscoveryDetails( + pathID: pathID, + discoveryType: .courseDetail(pathID), + sourceScreen: .discovery + ) + } else if case let .programDetails(pathID) = viewModel.sourceScreen { + viewModel.router.showWebDiscoveryDetails( + pathID: pathID, + discoveryType: .programDetail(pathID), + sourceScreen: .discovery + ) + } + + // Reseting the source screen + viewModel.sourceScreen = .discovery + } + } + + private var sourceScreen: LogistrationSourceScreen { + switch discoveryType { + case .discovery: + return .discovery + case .courseDetail(let pathID): + return .courseDetail(pathID, "") + case .programDetail(let pathID): + return .programDetails(pathID) + } + } +} + +fileprivate extension String { + func contains(find: String) -> Bool { + return range(of: find) != nil + } +} diff --git a/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift b/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift new file mode 100644 index 000000000..ca89f7d77 --- /dev/null +++ b/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift @@ -0,0 +1,36 @@ +// +// URL+PathExtension.swift +// Discovery +// +// Created by SaeedBashir on 12/18/23. +// + +import Foundation + +public extension URL { + var appURLHost: String { + return host ?? "" + } + + var isValidAppURLScheme: Bool { + return scheme ?? "" == URIString.appURLScheme.rawValue ? true : false + } + + var queryParameters: [String: Any]? { + guard let queryString = query else { + return nil + } + var queryParameters = [String: Any]() + let parameters = queryString.components(separatedBy: "&") + for parameter in parameters { + let keyValuePair = parameter.components(separatedBy: "=") + // Parameter will be ignored if invalid data for keyValuePair + if keyValuePair.count == 2 { + let key = keyValuePair[0] + let value = keyValuePair[1] + queryParameters[key] = value + } + } + return queryParameters + } +} diff --git a/Discovery/Discovery/SwiftGen/Strings.swift b/Discovery/Discovery/SwiftGen/Strings.swift index c53a55352..abe90994d 100644 --- a/Discovery/Discovery/SwiftGen/Strings.swift +++ b/Discovery/Discovery/SwiftGen/Strings.swift @@ -41,6 +41,12 @@ public enum DiscoveryLocalization { public static let updateRequiredTitle = DiscoveryLocalization.tr("Localizable", "UPDATE_REQUIRED_TITLE", fallback: "App Update Required") /// Why do I need to update? public static let updateWhyNeed = DiscoveryLocalization.tr("Localizable", "UPDATE_WHY_NEED", fallback: "Why do I need to update?") + public enum Alert { + /// You are now leaving the app and opening a browser + public static let leavingAppMessage = DiscoveryLocalization.tr("Localizable", "ALERT.LEAVING_APP_MESSAGE", fallback: "You are now leaving the app and opening a browser") + /// Leaving the app + public static let leavingAppTitle = DiscoveryLocalization.tr("Localizable", "ALERT.LEAVING_APP_TITLE", fallback: "Leaving the app") + } public enum Header { /// Discover new public static let title1 = DiscoveryLocalization.tr("Localizable", "HEADER.TITLE_1", fallback: "Discover new") diff --git a/Discovery/Discovery/en.lproj/Localizable.strings b/Discovery/Discovery/en.lproj/Localizable.strings index 074eb6cbd..bedd53a22 100644 --- a/Discovery/Discovery/en.lproj/Localizable.strings +++ b/Discovery/Discovery/en.lproj/Localizable.strings @@ -26,3 +26,6 @@ "UPDATE_NEEDED_NOT_NOW" = "Not Now"; "UPDATE_NEW_AVALIABLE" = "New update available! Upgrade now to receive the latest features and fixes"; + +"ALERT.LEAVING_APP_TITLE" = "Leaving the app"; +"ALERT.LEAVING_APP_MESSAGE" = "You are now leaving the app and opening a browser"; diff --git a/Discovery/Discovery/uk.lproj/Localizable.strings b/Discovery/Discovery/uk.lproj/Localizable.strings index fd43d1635..63109fae9 100644 --- a/Discovery/Discovery/uk.lproj/Localizable.strings +++ b/Discovery/Discovery/uk.lproj/Localizable.strings @@ -26,3 +26,6 @@ "UPDATE_NEEDED_NOT_NOW" = "Не зараз"; "UPDATE_NEW_AVALIABLE" = "Доступне нове оновлення! Оновіть зараз, щоб отримати найновіші функції та виправлення"; + +"ALERT.LEAVING_APP_TITLE" = "Leaving the app"; +"ALERT.LEAVING_APP_MESSAGE" = "You are now leaving the app and opening a browser"; diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index 710764940..d3470e0ce 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -109,6 +109,18 @@ class ScreenAssembly: Assembly { ) } + container.register(DiscoveryWebviewModel.self) { r, sourceScreen in + DiscoveryWebviewModel( + router: r.resolve(DiscoveryRouter.self)!, + config: r.resolve(ConfigProtocol.self)!, + interactor: r.resolve(CourseInteractorProtocol.self)!, + connectivity: r.resolve(ConnectivityProtocol.self)!, + analytics: r.resolve(DiscoveryAnalytics.self)!, + storage: r.resolve(CoreStorage.self)!, + sourceScreen: sourceScreen + ) + } + container.register(SearchViewModel.self) { r in SearchViewModel( interactor: r.resolve(DiscoveryInteractorProtocol.self)!, diff --git a/OpenEdX/Info.plist b/OpenEdX/Info.plist index e4bdcba95..b74f3967c 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -20,6 +20,11 @@ fastmail protonmail + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + UIAppFonts UIViewControllerBasedStatusBarAppearance diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index 5e69b64ad..d5d34219e 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -210,6 +210,26 @@ public class Router: AuthorizationRouter, navigationController.pushViewController(controller, animated: true) } + public func showWebDiscoveryDetails( + pathID: String, + discoveryType: DiscoveryWebviewType, + sourceScreen: LogistrationSourceScreen + ) { + let view = DiscoveryWebview( + viewModel: Container.shared.resolve( + DiscoveryWebviewModel.self, + argument: sourceScreen)!, + router: Container.shared.resolve(DiscoveryRouter.self)!, + discoveryType: discoveryType, + pathID: pathID + ) + + DispatchQueue.main.async { [weak self] in + let controller = UIHostingController(rootView: view) + self?.navigationController.pushViewController(controller, animated: true) + } + } + public func showDiscoverySearch(searchQuery: String? = nil) { let viewModel = Container.shared.resolve(SearchViewModel.self)! let view = SearchView(viewModel: viewModel, searchQuery: searchQuery) @@ -219,14 +239,29 @@ public class Router: AuthorizationRouter, } public func showDiscoveryScreen(searchQuery: String? = nil, sourceScreen: LogistrationSourceScreen) { - let view = DiscoveryView( - viewModel: Container.shared.resolve(DiscoveryViewModel.self)!, - router: Container.shared.resolve(DiscoveryRouter.self)!, - searchQuery: searchQuery, - sourceScreen: sourceScreen - ) - let controller = UIHostingController(rootView: view) - navigationController.pushViewController(controller, animated: true) + let config = Container.shared.resolve(ConfigProtocol.self) + if config?.discovery.type == .native { + let view = DiscoveryView( + viewModel: Container.shared.resolve(DiscoveryViewModel.self)!, + router: Container.shared.resolve(DiscoveryRouter.self)!, + searchQuery: searchQuery, + sourceScreen: sourceScreen + ) + let controller = UIHostingController(rootView: view) + navigationController.pushViewController(controller, animated: true) + } else if config?.discovery.type == .webview { + let view = DiscoveryWebview( + viewModel: Container.shared.resolve( + DiscoveryWebviewModel.self, + argument: sourceScreen + )!, + router: Container.shared.resolve(DiscoveryRouter.self)!, + searchQuery: searchQuery + ) + + let controller = UIHostingController(rootView: view) + navigationController.pushViewController(controller, animated: true) + } } public func showDiscussionsSearch(courseID: String) { diff --git a/OpenEdX/View/MainScreenView.swift b/OpenEdX/View/MainScreenView.swift index 6f26a929e..13d524081 100644 --- a/OpenEdX/View/MainScreenView.swift +++ b/OpenEdX/View/MainScreenView.swift @@ -42,11 +42,22 @@ struct MainScreenView: View { var body: some View { TabView(selection: $selection) { ZStack { - DiscoveryView( - viewModel: Container.shared.resolve(DiscoveryViewModel.self)!, - router: Container.shared.resolve(DiscoveryRouter.self)!, - sourceScreen: viewModel.sourceScreen - ) + let config = Container.shared.resolve(ConfigProtocol.self) + if config?.discovery.type == .native { + DiscoveryView( + viewModel: Container.shared.resolve(DiscoveryViewModel.self)!, + router: Container.shared.resolve(DiscoveryRouter.self)!, + sourceScreen: viewModel.sourceScreen + ) + } else if config?.discovery.type == .webview { + DiscoveryWebview( + viewModel: Container.shared.resolve( + DiscoveryWebviewModel.self, + argument: viewModel.sourceScreen)!, + router: Container.shared.resolve(DiscoveryRouter.self)! + ) + } + if updateAvaliable { UpdateNotificationView(config: viewModel.config) } From c54220221b6622f6695fefa3a7bcec921e6579c9 Mon Sep 17 00:00:00 2001 From: saeedbashir Date: Mon, 8 Jan 2024 16:06:33 +0500 Subject: [PATCH 02/14] fix: update framework settings --- Core/Core.xcodeproj/project.pbxproj | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index f93630d95..7db7f3ecd 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -142,14 +142,10 @@ DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */; }; DBF6F24A2B0380E00098414B /* FeaturesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2492B0380E00098414B /* FeaturesConfig.swift */; }; E055A5392B18DC95008D9E5E /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E055A5382B18DC95008D9E5E /* Theme.framework */; }; - E055A53A2B18DC95008D9E5E /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E055A5382B18DC95008D9E5E /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E09179FD2B0F204E002AB695 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09179FC2B0F204D002AB695 /* ConfigTests.swift */; }; - E0D586362B314CD3009B4BA7 /* LogistrationBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */; }; E0D5861A2B2FF74C009B4BA7 /* DiscoveryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */; }; E0D5861C2B2FF85B009B4BA7 /* RawStringExtactable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */; }; E0D586362B314CD3009B4BA7 /* LogistrationBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */; }; - E0D5861A2B2FF74C009B4BA7 /* DiscoveryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */; }; - E0D5861C2B2FF85B009B4BA7 /* RawStringExtactable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -162,20 +158,6 @@ }; /* End PBXContainerItemProxy section */ -/* Begin PBXCopyFilesBuildPhase section */ - E055A53B2B18DC95008D9E5E /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - E055A53A2B18DC95008D9E5E /* Theme.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ 020306CB2932C0C4000949EA /* PickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerView.swift; sourceTree = ""; }; 02066B472906F73400F4307E /* PickerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerMenu.swift; sourceTree = ""; }; @@ -320,12 +302,9 @@ DBF6F2492B0380E00098414B /* FeaturesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesConfig.swift; sourceTree = ""; }; E055A5382B18DC95008D9E5E /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E09179FC2B0F204D002AB695 /* ConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; - E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogistrationBottomView.swift; sourceTree = ""; }; E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryConfig.swift; sourceTree = ""; }; E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawStringExtactable.swift; sourceTree = ""; }; E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogistrationBottomView.swift; sourceTree = ""; }; - E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryConfig.swift; sourceTree = ""; }; - E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawStringExtactable.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -793,7 +772,6 @@ 0770DE0428D07831006D8A5D /* Sources */, 0770DE0528D07831006D8A5D /* Frameworks */, 0770DE0628D07831006D8A5D /* Resources */, - E055A53B2B18DC95008D9E5E /* Embed Frameworks */, ); buildRules = ( ); @@ -1016,7 +994,6 @@ 0233D5712AF13EC800BAC8BD /* SelectMailClientView.swift in Sources */, BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */, 02B2B594295C5C7A00914876 /* Thread.swift in Sources */, - 027DB33528D8C8FE002B6862 /* Data_MyCourse.swift in Sources */, E0D5861C2B2FF85B009B4BA7 /* RawStringExtactable.swift in Sources */, 027BD3BD2909478B00392132 /* UIView+EnclosingScrollView.swift in Sources */, BA8FA6682AD59A5700EA029A /* SocialAuthButton.swift in Sources */, From 634b73b6cdef2bd011bc9d8c7dba55518593e308 Mon Sep 17 00:00:00 2001 From: saeedbashir Date: Wed, 10 Jan 2024 09:59:59 +0500 Subject: [PATCH 03/14] chore: moving course detail related code to Discovery framwrok --- .../Config/DiscoveryConfig.swift | 2 +- Course/Course.xcodeproj/project.pbxproj | 22 ------ Course/Course/Data/CourseRepository.swift | 65 ---------------- .../Course/Data/Network/CourseEndpoint.swift | 21 ------ .../CourseCoreModel.xcdatamodel/contents | 20 +---- .../CoursePersistenceProtocol.swift | 2 - Course/Course/Domain/CourseInteractor.swift | 15 ---- .../Course/Presentation/CourseAnalytics.swift | 6 -- Course/Course/Presentation/CourseRouter.swift | 20 ----- Course/Course/SwiftGen/Strings.swift | 18 +---- Course/Course/en.lproj/Localizable.strings | 5 -- Discovery/Discovery.xcodeproj/project.pbxproj | 66 ++++++++++++----- .../Discovery/Data/DiscoveryRepository.swift | 74 ++++++++++++++++++- .../Discovery/Data}/Model/CourseDetails.swift | 0 .../Model/Data_CourseDetailsResponse.swift | 0 .../Data/Network/DiscoveryEndpoint.swift | 24 +++++- .../DiscoveryCoreModel.xcdatamodel/contents | 20 ++++- .../DiscoveryPersistenceProtocol.swift | 2 + .../Domain/DiscoveryInteractor.swift | 15 ++++ .../Presentation/DiscoveryAnalytics.swift | 6 ++ .../Presentation/DiscoveryRouter.swift | 18 +++++ .../NativeDiscovery}/CourseDetailsView.swift | 14 ++-- .../CourseDetailsViewModel.swift | 12 +-- .../{ => NativeDiscovery}/DiscoveryView.swift | 0 .../DiscoveryViewModel.swift | 0 .../{ => NativeDiscovery}/SearchView.swift | 0 .../SearchViewModel.swift | 0 .../WebDiscovery/DiscoverWebviewModel.swift | 21 +++--- Discovery/Discovery/SwiftGen/Strings.swift | 10 +++ .../Discovery/en.lproj/Localizable.strings | 5 ++ OpenEdX/DI/ScreenAssembly.swift | 8 +- OpenEdX/Data/CoursePersistence.swift | 41 ---------- OpenEdX/Data/DiscoveryPersistence.swift | 41 ++++++++++ OpenEdX/View/MainScreenView.swift | 48 ++++++------ 34 files changed, 315 insertions(+), 306 deletions(-) rename {Course/Course/Domain => Discovery/Discovery/Data}/Model/CourseDetails.swift (100%) rename {Course/Course => Discovery/Discovery}/Data/Model/Data_CourseDetailsResponse.swift (100%) rename {Course/Course/Presentation/Details => Discovery/Discovery/Presentation/NativeDiscovery}/CourseDetailsView.swift (97%) rename {Course/Course/Presentation/Details => Discovery/Discovery/Presentation/NativeDiscovery}/CourseDetailsViewModel.swift (94%) rename Discovery/Discovery/Presentation/{ => NativeDiscovery}/DiscoveryView.swift (100%) rename Discovery/Discovery/Presentation/{ => NativeDiscovery}/DiscoveryViewModel.swift (100%) rename Discovery/Discovery/Presentation/{ => NativeDiscovery}/SearchView.swift (100%) rename Discovery/Discovery/Presentation/{ => NativeDiscovery}/SearchViewModel.swift (100%) diff --git a/Core/Core/Configuration/Config/DiscoveryConfig.swift b/Core/Core/Configuration/Config/DiscoveryConfig.swift index 592ba09ee..e464ba220 100644 --- a/Core/Core/Configuration/Config/DiscoveryConfig.swift +++ b/Core/Core/Configuration/Config/DiscoveryConfig.swift @@ -44,7 +44,7 @@ public class DiscoveryConfig: NSObject { webview = DiscoveryWebviewConfig(dictionary: dictionary[DiscoveryKeys.webview] as? [String: AnyObject] ?? [:]) } - var isEnabled: Bool { + public var enabled: Bool { return type != .none } } diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index 61b4587a4..c7bd05938 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -45,11 +45,7 @@ 02B6B3B728E1D11E00232911 /* CourseInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3B628E1D11E00232911 /* CourseInteractor.swift */; }; 02B6B3BC28E1D14F00232911 /* CourseRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3BB28E1D14F00232911 /* CourseRepository.swift */; }; 02B6B3BE28E1D15C00232911 /* CourseEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3BD28E1D15C00232911 /* CourseEndpoint.swift */; }; - 02B6B3C128E1DBA100232911 /* Data_CourseDetailsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3C028E1DBA100232911 /* Data_CourseDetailsResponse.swift */; }; - 02B6B3C328E1DCD100232911 /* CourseDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3C228E1DCD100232911 /* CourseDetails.swift */; }; 02B6B3C928E1E68100232911 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B6B3C828E1E68100232911 /* Core.framework */; }; - 02E685BE28E4B60A000AE015 /* CourseDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E685BD28E4B60A000AE015 /* CourseDetailsView.swift */; }; - 02E685C028E4B629000AE015 /* CourseDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E685BF28E4B629000AE015 /* CourseDetailsViewModel.swift */; }; 02F0144F28F46474002E513D /* CourseContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F0144E28F46474002E513D /* CourseContainerView.swift */; }; 02F0145728F4A2FF002E513D /* CourseContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F0145628F4A2FF002E513D /* CourseContainerViewModel.swift */; }; 02F066E829DC71750073E13B /* SubtittlesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F066E729DC71750073E13B /* SubtittlesView.swift */; }; @@ -128,11 +124,8 @@ 02B6B3B628E1D11E00232911 /* CourseInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseInteractor.swift; sourceTree = ""; }; 02B6B3BB28E1D14F00232911 /* CourseRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseRepository.swift; sourceTree = ""; }; 02B6B3BD28E1D15C00232911 /* CourseEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseEndpoint.swift; sourceTree = ""; }; - 02B6B3C028E1DBA100232911 /* Data_CourseDetailsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_CourseDetailsResponse.swift; sourceTree = ""; }; 02B6B3C228E1DCD100232911 /* CourseDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetails.swift; sourceTree = ""; }; 02B6B3C828E1E68100232911 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 02E685BD28E4B60A000AE015 /* CourseDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CourseDetailsView.swift; path = Course/Presentation/Details/CourseDetailsView.swift; sourceTree = SOURCE_ROOT; }; - 02E685BF28E4B629000AE015 /* CourseDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CourseDetailsViewModel.swift; path = Course/Presentation/Details/CourseDetailsViewModel.swift; sourceTree = SOURCE_ROOT; }; 02ED50CF29A64BB6008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F0144E28F46474002E513D /* CourseContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseContainerView.swift; sourceTree = ""; }; 02F0145628F4A2FF002E513D /* CourseContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseContainerViewModel.swift; sourceTree = ""; }; @@ -311,7 +304,6 @@ 02B6B3BF28E1DB8800232911 /* Model */ = { isa = PBXGroup; children = ( - 02B6B3C028E1DBA100232911 /* Data_CourseDetailsResponse.swift */, 0276D75A29DDA3890004CDF8 /* Data_ResumeBlock.swift */, 027020FB28E7362100F54332 /* Data_CourseOutlineResponse.swift */, 022C64DB29ACFDEE000F532B /* Data_HandoutsResponse.swift */, @@ -347,7 +339,6 @@ children = ( DB7D6EAA2ADFCAA00036BB13 /* Dates */, 070019A828F6F33600D5FC78 /* Container */, - 070019A628F6F2CB00D5FC78 /* Details */, 070019A728F6F2D600D5FC78 /* Outline */, 02DFC65029ACC20A00EA4BB9 /* Handouts */, 070019A928F6F59D00D5FC78 /* Unit */, @@ -381,15 +372,6 @@ path = Model; sourceTree = ""; }; - 070019A628F6F2CB00D5FC78 /* Details */ = { - isa = PBXGroup; - children = ( - 02E685BD28E4B60A000AE015 /* CourseDetailsView.swift */, - 02E685BF28E4B629000AE015 /* CourseDetailsViewModel.swift */, - ); - path = Details; - sourceTree = ""; - }; 070019A728F6F2D600D5FC78 /* Outline */ = { isa = PBXGroup; children = ( @@ -761,7 +743,6 @@ 0231124D28EDA804002588FB /* CourseUnitView.swift in Sources */, 027020FC28E7362100F54332 /* Data_CourseOutlineResponse.swift in Sources */, DB7D6EB02ADFDA0E0036BB13 /* CourseDates.swift in Sources */, - 02E685C028E4B629000AE015 /* CourseDetailsViewModel.swift in Sources */, 0295C889299BBE8200ABE571 /* CourseNavigationView.swift in Sources */, 06FD7EDF2B1F29F3008D632B /* CourseVerticalImageView.swift in Sources */, DB7D6EAE2ADFCB4A0036BB13 /* CourseDatesViewModel.swift in Sources */, @@ -773,14 +754,11 @@ 022C64D829ACEC48000F532B /* HandoutsView.swift in Sources */, 02454CA82A2619890043052A /* DiscussionView.swift in Sources */, 0265B4B728E2141D00E6EAFD /* Strings.swift in Sources */, - 02B6B3C128E1DBA100232911 /* Data_CourseDetailsResponse.swift in Sources */, DB7D6EAC2ADFCAC50036BB13 /* CourseDatesView.swift in Sources */, 0766DFCC299AA7A600EBEF6A /* YouTubeVideoPlayer.swift in Sources */, 022F8E162A1DFBC6008EFAB9 /* YouTubeVideoPlayerViewModel.swift in Sources */, - 02E685BE28E4B60A000AE015 /* CourseDetailsView.swift in Sources */, 02F175372A4DAFD20019CD70 /* CourseAnalytics.swift in Sources */, 02B6B3BE28E1D15C00232911 /* CourseEndpoint.swift in Sources */, - 02B6B3C328E1DCD100232911 /* CourseDetails.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Course/Course/Data/CourseRepository.swift b/Course/Course/Data/CourseRepository.swift index e21456a1f..7a154fa0e 100644 --- a/Course/Course/Data/CourseRepository.swift +++ b/Course/Course/Data/CourseRepository.swift @@ -9,11 +9,8 @@ import Foundation import Core public protocol CourseRepositoryProtocol { - func getCourseDetails(courseID: String) async throws -> CourseDetails func getCourseBlocks(courseID: String) async throws -> CourseStructure - func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails func getLoadedCourseBlocks(courseID: String) throws -> CourseStructure - func enrollToCourse(courseID: String) async throws -> Bool func blockCompletionRequest(courseID: String, blockID: String) async throws func getHandouts(courseID: String) async throws -> String? func getUpdates(courseID: String) async throws -> [CourseUpdate] @@ -41,21 +38,6 @@ public class CourseRepository: CourseRepositoryProtocol { self.config = config self.persistence = persistence } - - public func getCourseDetails(courseID: String) async throws -> CourseDetails { - let response = try await api.requestData( - CourseEndpoint.getCourseDetail(courseID: courseID, username: coreStorage.user?.username ?? "") - ).mapResponse(DataLayer.CourseDetailsResponse.self) - .domain(baseURL: config.baseURL.absoluteString) - - persistence.saveCourseDetails(course: response) - - return response - } - - public func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails { - return try persistence.loadCourseDetails(courseID: courseID) - } public func getCourseBlocks(courseID: String) async throws -> CourseStructure { let course = try await api.requestData( @@ -71,15 +53,6 @@ public class CourseRepository: CourseRepositoryProtocol { return parseCourseStructure(course: localData) } - public func enrollToCourse(courseID: String) async throws -> Bool { - let enroll = try await api.request(CourseEndpoint.enrollToCourse(courseID: courseID)) - if enroll.statusCode == 200 { - return true - } else { - return false - } - } - public func blockCompletionRequest(courseID: String, blockID: String) async throws { try await api.requestData(CourseEndpoint.blockCompletionRequest( username: coreStorage.user?.username ?? "", @@ -264,46 +237,12 @@ class CourseRepositoryMock: CourseRepositoryProtocol { } } - func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails { - return CourseDetails( - courseID: "courseID", - org: "Organization", - courseTitle: "Course title", - courseDescription: "Course description", - courseStart: Date(iso8601: "2021-05-26T12:13:14Z"), - courseEnd: Date(iso8601: "2022-05-26T12:13:14Z"), - enrollmentStart: nil, - enrollmentEnd: nil, - isEnrolled: false, - overviewHTML: "Course description

Lorem ipsum", - courseBannerURL: "courseBannerURL", - courseVideoURL: nil - ) - } - func getLoadedCourseBlocks(courseID: String) throws -> CourseStructure { let decoder = JSONDecoder() let jsonData = Data(courseStructureJson.utf8) let courseBlocks = try decoder.decode(DataLayer.CourseStructure.self, from: jsonData) return parseCourseStructure(course: courseBlocks) } - - public func getCourseDetails(courseID: String) async throws -> CourseDetails { - return CourseDetails( - courseID: "courseID", - org: "Organization", - courseTitle: "Course title", - courseDescription: "Course description", - courseStart: Date(iso8601: "2021-05-26T12:13:14Z"), - courseEnd: Date(iso8601: "2022-05-26T12:13:14Z"), - enrollmentStart: nil, - enrollmentEnd: nil, - isEnrolled: false, - overviewHTML: "Course description

Lorem ipsum", - courseBannerURL: "courseBannerURL", - courseVideoURL: nil - ) - } public func getCourseBlocks(courseID: String) async throws -> CourseStructure { do { @@ -314,10 +253,6 @@ class CourseRepositoryMock: CourseRepositoryProtocol { } } - public func enrollToCourse(courseID: String) async throws -> Bool { - return true - } - public func blockCompletionRequest(courseID: String, blockID: String) { } diff --git a/Course/Course/Data/Network/CourseEndpoint.swift b/Course/Course/Data/Network/CourseEndpoint.swift index 3253501e7..cdeedcd2d 100644 --- a/Course/Course/Data/Network/CourseEndpoint.swift +++ b/Course/Course/Data/Network/CourseEndpoint.swift @@ -10,10 +10,8 @@ import Core import Alamofire enum CourseEndpoint: EndPointType { - case getCourseDetail(courseID: String, username: String) case getCourseBlocks(courseID: String, userName: String) case pageHTML(pageUrlString: String) - case enrollToCourse(courseID: String) case blockCompletionRequest(username: String, courseID: String, blockID: String) case getHandouts(courseID: String) case getUpdates(courseID: String) @@ -23,14 +21,10 @@ enum CourseEndpoint: EndPointType { var path: String { switch self { - case .getCourseDetail(let courseID, _): - return "/api/courses/v1/courses/\(courseID)" case .getCourseBlocks: return "/api/mobile/v3/course_info/blocks/" case .pageHTML(let url): return "/xblock/\(url)" - case .enrollToCourse: - return "/api/enrollment/v1/enrollment" case .blockCompletionRequest: return "/api/completion/v1/completion-batch" case let .getHandouts(courseID): @@ -48,14 +42,10 @@ enum CourseEndpoint: EndPointType { var httpMethod: HTTPMethod { switch self { - case .getCourseDetail: - return .get case .getCourseBlocks: return .get case .pageHTML: return .get - case .enrollToCourse: - return .post case .blockCompletionRequest: return .post case .getHandouts: @@ -77,9 +67,6 @@ enum CourseEndpoint: EndPointType { var task: HTTPTask { switch self { - case let .getCourseDetail(_, username): - let params: [String: Encodable] = ["username": username] - return .requestParameters(parameters: params, encoding: URLEncoding.queryString) case let .getCourseBlocks(courseID, userName): let params: [String: Encodable] = [ "username": userName, @@ -96,14 +83,6 @@ enum CourseEndpoint: EndPointType { return .requestParameters(parameters: params, encoding: URLEncoding.queryString) case .pageHTML: return .request - case .enrollToCourse(courseID: let courseID): - let params: [String: Any] = [ - "course_details": [ - "course_id": courseID, - "email_opt_in": true - ] - ] - return .requestParameters(parameters: params, encoding: JSONEncoding.default) case let .blockCompletionRequest(username, courseID, blockID): let params: [String: Any] = [ "username": username, diff --git a/Course/Course/Data/Persistence/CourseCoreModel.xcdatamodeld/CourseCoreModel.xcdatamodel/contents b/Course/Course/Data/Persistence/CourseCoreModel.xcdatamodeld/CourseCoreModel.xcdatamodel/contents index 33202dee9..c24fa4e5a 100644 --- a/Course/Course/Data/Persistence/CourseCoreModel.xcdatamodeld/CourseCoreModel.xcdatamodel/contents +++ b/Course/Course/Data/Persistence/CourseCoreModel.xcdatamodeld/CourseCoreModel.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -44,24 +44,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/Course/Course/Data/Persistence/CoursePersistenceProtocol.swift b/Course/Course/Data/Persistence/CoursePersistenceProtocol.swift index 9efb9d435..35f8328c2 100644 --- a/Course/Course/Data/Persistence/CoursePersistenceProtocol.swift +++ b/Course/Course/Data/Persistence/CoursePersistenceProtocol.swift @@ -9,8 +9,6 @@ import CoreData import Core public protocol CoursePersistenceProtocol { - func loadCourseDetails(courseID: String) throws -> CourseDetails - func saveCourseDetails(course: CourseDetails) func loadEnrollments() throws -> [Core.CourseItem] func saveEnrollments(items: [Core.CourseItem]) func loadCourseStructure(courseID: String) throws -> DataLayer.CourseStructure diff --git a/Course/Course/Domain/CourseInteractor.swift b/Course/Course/Domain/CourseInteractor.swift index 3e3b18f71..aa2acabac 100644 --- a/Course/Course/Domain/CourseInteractor.swift +++ b/Course/Course/Domain/CourseInteractor.swift @@ -10,12 +10,9 @@ import Core //sourcery: AutoMockable public protocol CourseInteractorProtocol { - func getCourseDetails(courseID: String) async throws -> CourseDetails func getCourseBlocks(courseID: String) async throws -> CourseStructure func getCourseVideoBlocks(fullStructure: CourseStructure) -> CourseStructure - func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails func getLoadedCourseBlocks(courseID: String) async throws -> CourseStructure - func enrollToCourse(courseID: String) async throws -> Bool func blockCompletionRequest(courseID: String, blockID: String) async throws func getHandouts(courseID: String) async throws -> String? func getUpdates(courseID: String) async throws -> [CourseUpdate] @@ -32,10 +29,6 @@ public class CourseInteractor: CourseInteractorProtocol { self.repository = repository } - public func getCourseDetails(courseID: String) async throws -> CourseDetails { - return try await repository.getCourseDetails(courseID: courseID) - } - public func getCourseBlocks(courseID: String) async throws -> CourseStructure { return try await repository.getCourseBlocks(courseID: courseID) } @@ -62,18 +55,10 @@ public class CourseInteractor: CourseInteractorProtocol { ) } - public func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails { - return try await repository.getLoadedCourseDetails(courseID: courseID) - } - public func getLoadedCourseBlocks(courseID: String) async throws -> CourseStructure { return try repository.getLoadedCourseBlocks(courseID: courseID) } - public func enrollToCourse(courseID: String) async throws -> Bool { - return try await repository.enrollToCourse(courseID: courseID) - } - public func blockCompletionRequest(courseID: String, blockID: String) async throws { return try await repository.blockCompletionRequest(courseID: courseID, blockID: blockID) } diff --git a/Course/Course/Presentation/CourseAnalytics.swift b/Course/Course/Presentation/CourseAnalytics.swift index 7396438b4..ca7e66db7 100644 --- a/Course/Course/Presentation/CourseAnalytics.swift +++ b/Course/Course/Presentation/CourseAnalytics.swift @@ -9,9 +9,6 @@ import Foundation //sourcery: AutoMockable public protocol CourseAnalytics { - func courseEnrollClicked(courseId: String, courseName: String) - func courseEnrollSuccess(courseId: String, courseName: String) - func viewCourseClicked(courseId: String, courseName: String) func resumeCourseTapped(courseId: String, courseName: String, blockId: String) func sequentialClicked(courseId: String, courseName: String, blockId: String, blockName: String) func verticalClicked(courseId: String, courseName: String, blockId: String, blockName: String) @@ -29,9 +26,6 @@ public protocol CourseAnalytics { #if DEBUG class CourseAnalyticsMock: CourseAnalytics { - public func courseEnrollClicked(courseId: String, courseName: String) {} - public func courseEnrollSuccess(courseId: String, courseName: String) {} - public func viewCourseClicked(courseId: String, courseName: String) {} public func resumeCourseTapped(courseId: String, courseName: String, blockId: String) {} public func sequentialClicked(courseId: String, courseName: String, blockId: String, blockName: String) {} public func verticalClicked(courseId: String, courseName: String, blockId: String, blockName: String) {} diff --git a/Course/Course/Presentation/CourseRouter.swift b/Course/Course/Presentation/CourseRouter.swift index 065d5395f..35619bc2d 100644 --- a/Course/Course/Presentation/CourseRouter.swift +++ b/Course/Course/Presentation/CourseRouter.swift @@ -12,16 +12,6 @@ public protocol CourseRouter: BaseRouter { func presentAppReview() - func showCourseScreens( - courseID: String, - isActive: Bool?, - courseStart: Date?, - courseEnd: Date?, - enrollmentStart: Date?, - enrollmentEnd: Date?, - title: String - ) - func showCourseUnit( courseName: String, blockId: String, @@ -75,16 +65,6 @@ public class CourseRouterMock: BaseRouterMock, CourseRouter { public func presentAppReview() {} - public func showCourseScreens( - courseID: String, - isActive: Bool?, - courseStart: Date?, - courseEnd: Date?, - enrollmentStart: Date?, - enrollmentEnd: Date?, - title: String - ) {} - public func showCourseUnit( courseName: String, blockId: String, diff --git a/Course/Course/SwiftGen/Strings.swift b/Course/Course/SwiftGen/Strings.swift index 8da16a132..2af8e335c 100644 --- a/Course/Course/SwiftGen/Strings.swift +++ b/Course/Course/SwiftGen/Strings.swift @@ -60,19 +60,6 @@ public enum CourseLocalization { /// Videos public static let videos = CourseLocalization.tr("Localizable", "COURSE_CONTAINER.VIDEOS", fallback: "Videos") } - public enum Details { - /// Enroll now - public static let enrollNow = CourseLocalization.tr("Localizable", "DETAILS.ENROLL_NOW", fallback: "Enroll now") - /// You cannot enroll in this course because the enrollment date is over. - public static let enrollmentDateIsOver = CourseLocalization.tr("Localizable", "DETAILS.ENROLLMENT_DATE_IS_OVER", fallback: "You cannot enroll in this course because the enrollment date is over.") - /// Localizable.strings - /// Course - /// - /// Created by  Stepanok Ivan on 26.09.2022. - public static let title = CourseLocalization.tr("Localizable", "DETAILS.TITLE", fallback: "Course details") - /// View course - public static let viewCourse = CourseLocalization.tr("Localizable", "DETAILS.VIEW_COURSE", fallback: "View course") - } public enum Error { /// Course component not found, please reload public static let componentNotFount = CourseLocalization.tr("Localizable", "ERROR.COMPONENT_NOT_FOUNT", fallback: "Course component not found, please reload") @@ -104,7 +91,10 @@ public enum CourseLocalization { public enum Outline { /// Certificate public static let certificate = CourseLocalization.tr("Localizable", "OUTLINE.CERTIFICATE", fallback: "Certificate") - /// Congratulations! + /// Localizable.strings + /// Course + /// + /// Created by  Stepanok Ivan on 26.09.2022. public static let congratulations = CourseLocalization.tr("Localizable", "OUTLINE.CONGRATULATIONS", fallback: "Congratulations!") /// This course hasn't started yet. public static let courseHasntStarted = CourseLocalization.tr("Localizable", "OUTLINE.COURSE_HASNT_STARTED", fallback: "This course hasn't started yet.") diff --git a/Course/Course/en.lproj/Localizable.strings b/Course/Course/en.lproj/Localizable.strings index 8fe3b7008..ea370aed7 100644 --- a/Course/Course/en.lproj/Localizable.strings +++ b/Course/Course/en.lproj/Localizable.strings @@ -6,11 +6,6 @@ */ -"DETAILS.TITLE" = "Course details"; -"DETAILS.VIEW_COURSE" = "View course"; -"DETAILS.ENROLL_NOW" = "Enroll now"; -"DETAILS.ENROLLMENT_DATE_IS_OVER" = "You cannot enroll in this course because the enrollment date is over."; - "OUTLINE.CONGRATULATIONS" = "Congratulations!"; "OUTLINE.PASSED_THE_COURSE" = "You've passed the course"; "OUTLINE.VIEW_CERTIFICATE" = "View certificate"; diff --git a/Discovery/Discovery.xcodeproj/project.pbxproj b/Discovery/Discovery.xcodeproj/project.pbxproj index 19cac5ced..fa6ef6028 100644 --- a/Discovery/Discovery.xcodeproj/project.pbxproj +++ b/Discovery/Discovery.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 022D048B2976D7E100E0059B /* Discovery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0727879928D34C03002E9142 /* Discovery.framework */; platformFilter = ios; }; 022D04982976DA8A00E0059B /* DiscoveryMock.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022D04972976DA8A00E0059B /* DiscoveryMock.generated.swift */; }; 0283347728D499BC00C828FC /* DiscoveryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283347628D499BC00C828FC /* DiscoveryInteractor.swift */; }; - 0283347928D49A8700C828FC /* DiscoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0283347828D49A8700C828FC /* DiscoveryViewModel.swift */; }; 0284DBFC28D4856A00830893 /* DiscoveryEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0284DBFB28D4856A00830893 /* DiscoveryEndpoint.swift */; }; 0284DC0328D4922900830893 /* DiscoveryRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0284DC0228D4922900830893 /* DiscoveryRepository.swift */; }; 029242E72AE6978400A940EC /* UpdateRequiredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029242E62AE6978400A940EC /* UpdateRequiredView.swift */; }; @@ -25,14 +24,18 @@ 02F1752F2A4DA3B60019CD70 /* DiscoveryAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F1752E2A4DA3B60019CD70 /* DiscoveryAnalytics.swift */; }; 02F3BFDF29252F2F0051930C /* DiscoveryRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3BFDE29252F2F0051930C /* DiscoveryRouter.swift */; }; 072787AD28D34D15002E9142 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 072787AC28D34D15002E9142 /* Core.framework */; }; - 072787B428D34D91002E9142 /* DiscoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 072787B328D34D91002E9142 /* DiscoveryView.swift */; }; 63C6E9CBBF5E33B8B9B4DFEC /* Pods_App_Discovery_DiscoveryUnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 780FC373E1D479E58870BD85 /* Pods_App_Discovery_DiscoveryUnitTests.framework */; }; 9F47BCC672941A9854404EC7 /* Pods_App_Discovery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 919E55130969D91EF03C4C0B /* Pods_App_Discovery.framework */; }; - CFC849432996A5150055E497 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC849422996A5150055E497 /* SearchView.swift */; }; - CFC849452996A52A0055E497 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC849442996A52A0055E497 /* SearchViewModel.swift */; }; CFC8494C299A66080055E497 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = CFC8494E299A66080055E497 /* Localizable.stringsdict */; }; CFC84950299BE52C0055E497 /* SearchViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC8494F299BE52C0055E497 /* SearchViewModelTests.swift */; }; - E08D12E32B482D720096311A /* Course.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E08D12E22B482D720096311A /* Course.framework */; }; + E0B9F69C2B4D57F800168366 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6962B4D57F800168366 /* SearchView.swift */; }; + E0B9F69D2B4D57F800168366 /* DiscoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6972B4D57F800168366 /* DiscoveryViewModel.swift */; }; + E0B9F69E2B4D57F800168366 /* CourseDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6982B4D57F800168366 /* CourseDetailsView.swift */; }; + E0B9F69F2B4D57F800168366 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6992B4D57F800168366 /* SearchViewModel.swift */; }; + E0B9F6A02B4D57F800168366 /* DiscoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F69A2B4D57F800168366 /* DiscoveryView.swift */; }; + E0B9F6A12B4D57F800168366 /* CourseDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F69B2B4D57F800168366 /* CourseDetailsViewModel.swift */; }; + E0B9F6A42B4D59E000168366 /* CourseDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6A32B4D59E000168366 /* CourseDetails.swift */; }; + E0B9F6A62B4D620100168366 /* Data_CourseDetailsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6A52B4D620100168366 /* Data_CourseDetailsResponse.swift */; }; E0D586202B300095009B4BA7 /* DiscoverWebviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */; }; E0D586212B300095009B4BA7 /* DiscoveryWebview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */; }; E0D586232B3000AD009B4BA7 /* DiscoveryURIDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586222B3000AD009B4BA7 /* DiscoveryURIDetails.swift */; }; @@ -54,7 +57,6 @@ 022D04892976D7E100E0059B /* DiscoveryViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryViewModelTests.swift; sourceTree = ""; }; 022D04972976DA8A00E0059B /* DiscoveryMock.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DiscoveryMock.generated.swift; path = DiscoveryTests/DiscoveryMock.generated.swift; sourceTree = SOURCE_ROOT; }; 0283347628D499BC00C828FC /* DiscoveryInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryInteractor.swift; sourceTree = ""; }; - 0283347828D49A8700C828FC /* DiscoveryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryViewModel.swift; sourceTree = ""; }; 0284DBFB28D4856A00830893 /* DiscoveryEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryEndpoint.swift; sourceTree = ""; }; 0284DC0228D4922900830893 /* DiscoveryRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryRepository.swift; sourceTree = ""; }; 029242E62AE6978400A940EC /* UpdateRequiredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateRequiredView.swift; sourceTree = ""; }; @@ -72,7 +74,6 @@ 0692409931272CDA39B10321 /* Pods-App-Discovery.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Discovery/Pods-App-Discovery.releasestage.xcconfig"; sourceTree = ""; }; 0727879928D34C03002E9142 /* Discovery.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Discovery.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 072787AC28D34D15002E9142 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 072787B328D34D91002E9142 /* DiscoveryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryView.swift; sourceTree = ""; }; 0C3850985F33C1AD72BF1B04 /* Pods-App-Discovery.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery.release.xcconfig"; path = "Target Support Files/Pods-App-Discovery/Pods-App-Discovery.release.xcconfig"; sourceTree = ""; }; 2334C76D248D0A95634AFFD9 /* Pods-App-Discovery-DiscoveryUnitTests.debugstage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery-DiscoveryUnitTests.debugstage.xcconfig"; path = "Target Support Files/Pods-App-Discovery-DiscoveryUnitTests/Pods-App-Discovery-DiscoveryUnitTests.debugstage.xcconfig"; sourceTree = ""; }; 2760B1F234E01FFCB73F41C2 /* Pods-App-Discovery.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery.debug.xcconfig"; path = "Target Support Files/Pods-App-Discovery/Pods-App-Discovery.debug.xcconfig"; sourceTree = ""; }; @@ -87,11 +88,17 @@ 919E55130969D91EF03C4C0B /* Pods_App_Discovery.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Discovery.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9F968F74AD7F4B5F6E5A6084 /* Pods-App-Discovery-DiscoveryUnitTests.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery-DiscoveryUnitTests.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Discovery-DiscoveryUnitTests/Pods-App-Discovery-DiscoveryUnitTests.debugdev.xcconfig"; sourceTree = ""; }; AAC0D83F5D34491E9FABCABC /* Pods-App-Discovery-DiscoveryUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discovery-DiscoveryUnitTests.debug.xcconfig"; path = "Target Support Files/Pods-App-Discovery-DiscoveryUnitTests/Pods-App-Discovery-DiscoveryUnitTests.debug.xcconfig"; sourceTree = ""; }; - CFC849422996A5150055E497 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; - CFC849442996A52A0055E497 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; CFC8494D299A66080055E497 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; CFC8494F299BE52C0055E497 /* SearchViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModelTests.swift; sourceTree = ""; }; E08D12E22B482D720096311A /* Course.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Course.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E0B9F6962B4D57F800168366 /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; + E0B9F6972B4D57F800168366 /* DiscoveryViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryViewModel.swift; sourceTree = ""; }; + E0B9F6982B4D57F800168366 /* CourseDetailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetailsView.swift; sourceTree = ""; }; + E0B9F6992B4D57F800168366 /* SearchViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; + E0B9F69A2B4D57F800168366 /* DiscoveryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryView.swift; sourceTree = ""; }; + E0B9F69B2B4D57F800168366 /* CourseDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetailsViewModel.swift; sourceTree = ""; }; + E0B9F6A32B4D59E000168366 /* CourseDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetails.swift; sourceTree = ""; }; + E0B9F6A52B4D620100168366 /* Data_CourseDetailsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data_CourseDetailsResponse.swift; sourceTree = ""; }; E0D586132B29F25A009B4BA7 /* Authorization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Authorization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverWebviewModel.swift; sourceTree = ""; }; E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryWebview.swift; sourceTree = ""; }; @@ -117,7 +124,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E08D12E32B482D720096311A /* Course.framework in Frameworks */, 072787AD28D34D15002E9142 /* Core.framework in Frameworks */, 9F47BCC672941A9854404EC7 /* Pods_App_Discovery.framework in Frameworks */, ); @@ -147,6 +153,7 @@ 0284DBF828D4831000830893 /* Data */ = { isa = PBXGroup; children = ( + E0B9F6A22B4D59E000168366 /* Model */, 0284DBFA28D4832D00830893 /* Network */, 0284DC0228D4922900830893 /* DiscoveryRepository.swift */, 0208666829CC6CD600BC05B2 /* Persistence */, @@ -191,12 +198,9 @@ 070019A228F6EF2700D5FC78 /* Presentation */ = { isa = PBXGroup; children = ( + E0B9F6952B4D57F800168366 /* NativeDiscovery */, E0D5861D2B300095009B4BA7 /* WebDiscovery */, 029242E52AE6976E00A940EC /* UpdateViews */, - 072787B328D34D91002E9142 /* DiscoveryView.swift */, - 0283347828D49A8700C828FC /* DiscoveryViewModel.swift */, - CFC849422996A5150055E497 /* SearchView.swift */, - CFC849442996A52A0055E497 /* SearchViewModel.swift */, 02F3BFDE29252F2F0051930C /* DiscoveryRouter.swift */, 02F1752E2A4DA3B60019CD70 /* DiscoveryAnalytics.swift */, ); @@ -283,6 +287,28 @@ path = ../Pods; sourceTree = ""; }; + E0B9F6952B4D57F800168366 /* NativeDiscovery */ = { + isa = PBXGroup; + children = ( + E0B9F6962B4D57F800168366 /* SearchView.swift */, + E0B9F6972B4D57F800168366 /* DiscoveryViewModel.swift */, + E0B9F6982B4D57F800168366 /* CourseDetailsView.swift */, + E0B9F6992B4D57F800168366 /* SearchViewModel.swift */, + E0B9F69A2B4D57F800168366 /* DiscoveryView.swift */, + E0B9F69B2B4D57F800168366 /* CourseDetailsViewModel.swift */, + ); + path = NativeDiscovery; + sourceTree = ""; + }; + E0B9F6A22B4D59E000168366 /* Model */ = { + isa = PBXGroup; + children = ( + E0B9F6A52B4D620100168366 /* Data_CourseDetailsResponse.swift */, + E0B9F6A32B4D59E000168366 /* CourseDetails.swift */, + ); + path = Model; + sourceTree = ""; + }; E0D5861D2B300095009B4BA7 /* WebDiscovery */ = { isa = PBXGroup; children = ( @@ -503,22 +529,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CFC849452996A52A0055E497 /* SearchViewModel.swift in Sources */, 029242E92AE6A3AB00A940EC /* UpdateRecommendedView.swift in Sources */, - CFC849432996A5150055E497 /* SearchView.swift in Sources */, + E0B9F6A42B4D59E000168366 /* CourseDetails.swift in Sources */, 0284DBFC28D4856A00830893 /* DiscoveryEndpoint.swift in Sources */, 029737402949FB070051696B /* DiscoveryCoreModel.xcdatamodeld in Sources */, 029242E72AE6978400A940EC /* UpdateRequiredView.swift in Sources */, + E0B9F69F2B4D57F800168366 /* SearchViewModel.swift in Sources */, + E0B9F69E2B4D57F800168366 /* CourseDetailsView.swift in Sources */, E0D586232B3000AD009B4BA7 /* DiscoveryURIDetails.swift in Sources */, + E0B9F69D2B4D57F800168366 /* DiscoveryViewModel.swift in Sources */, 0283347728D499BC00C828FC /* DiscoveryInteractor.swift in Sources */, + E0B9F6A02B4D57F800168366 /* DiscoveryView.swift in Sources */, + E0B9F6A12B4D57F800168366 /* CourseDetailsViewModel.swift in Sources */, 02F3BFDF29252F2F0051930C /* DiscoveryRouter.swift in Sources */, - 0283347928D49A8700C828FC /* DiscoveryViewModel.swift in Sources */, - 072787B428D34D91002E9142 /* DiscoveryView.swift in Sources */, E0D586252B300134009B4BA7 /* URL+PathExtension.swift in Sources */, + E0B9F6A62B4D620100168366 /* Data_CourseDetailsResponse.swift in Sources */, 029737422949FB3B0051696B /* DiscoveryPersistenceProtocol.swift in Sources */, 0284DC0328D4922900830893 /* DiscoveryRepository.swift in Sources */, 029242EB2AE6AB7B00A940EC /* UpdateNotificationView.swift in Sources */, 02EF39DC28D86BEF0058F6BD /* Strings.swift in Sources */, + E0B9F69C2B4D57F800168366 /* SearchView.swift in Sources */, E0D586202B300095009B4BA7 /* DiscoverWebviewModel.swift in Sources */, 02F1752F2A4DA3B60019CD70 /* DiscoveryAnalytics.swift in Sources */, E0D586212B300095009B4BA7 /* DiscoveryWebview.swift in Sources */, diff --git a/Discovery/Discovery/Data/DiscoveryRepository.swift b/Discovery/Discovery/Data/DiscoveryRepository.swift index bf898af35..f644af888 100644 --- a/Discovery/Discovery/Data/DiscoveryRepository.swift +++ b/Discovery/Discovery/Data/DiscoveryRepository.swift @@ -14,12 +14,15 @@ public protocol DiscoveryRepositoryProtocol { func getDiscovery(page: Int) async throws -> [CourseItem] func searchCourses(page: Int, searchTerm: String) async throws -> [CourseItem] func getDiscoveryOffline() throws -> [CourseItem] + func getCourseDetails(courseID: String) async throws -> CourseDetails + func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails + func enrollToCourse(courseID: String) async throws -> Bool } public class DiscoveryRepository: DiscoveryRepositoryProtocol { private let api: API - private let appStorage: CoreStorage + private let coreStorage: CoreStorage private let config: ConfigProtocol private let persistence: DiscoveryPersistenceProtocol @@ -28,14 +31,14 @@ public class DiscoveryRepository: DiscoveryRepositoryProtocol { config: ConfigProtocol, persistence: DiscoveryPersistenceProtocol) { self.api = api - self.appStorage = appStorage + self.coreStorage = appStorage self.config = config self.persistence = persistence } public func getDiscovery(page: Int) async throws -> [CourseItem] { let discoveryResponse = try await api.requestData(DiscoveryEndpoint.getDiscovery( - username: appStorage.user?.username ?? "", page: page) + username: coreStorage.user?.username ?? "", page: page) ).mapResponse(DataLayer.DiscoveryResponce.self).domain persistence.saveDiscovery(items: discoveryResponse) return discoveryResponse @@ -47,16 +50,79 @@ public class DiscoveryRepository: DiscoveryRepositoryProtocol { public func searchCourses(page: Int, searchTerm: String) async throws -> [CourseItem] { let searchResponse = try await api.requestData(DiscoveryEndpoint.searchCourses( - username: appStorage.user?.username ?? "", page: page, searchTerm: searchTerm) + username: coreStorage.user?.username ?? "", page: page, searchTerm: searchTerm) ).mapResponse(DataLayer.DiscoveryResponce.self).domain return searchResponse } + + public func getCourseDetails(courseID: String) async throws -> CourseDetails { + let response = try await api.requestData( + DiscoveryEndpoint.getCourseDetail(courseID: courseID, username: coreStorage.user?.username ?? "") + ).mapResponse(DataLayer.CourseDetailsResponse.self) + .domain(baseURL: config.baseURL.absoluteString) + + persistence.saveCourseDetails(course: response) + + return response + } + + public func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails { + return try persistence.loadCourseDetails(courseID: courseID) + } + + public func enrollToCourse(courseID: String) async throws -> Bool { + let enroll = try await api.request(DiscoveryEndpoint.enrollToCourse(courseID: courseID)) + if enroll.statusCode == 200 { + return true + } else { + return false + } + } } // Mark - For testing and SwiftUI preview #if DEBUG class DiscoveryRepositoryMock: DiscoveryRepositoryProtocol { + + public func getCourseDetails(courseID: String) async throws -> CourseDetails { + return CourseDetails( + courseID: "courseID", + org: "Organization", + courseTitle: "Course title", + courseDescription: "Course description", + courseStart: Date(iso8601: "2021-05-26T12:13:14Z"), + courseEnd: Date(iso8601: "2022-05-26T12:13:14Z"), + enrollmentStart: nil, + enrollmentEnd: nil, + isEnrolled: false, + overviewHTML: "Course description

Lorem ipsum", + courseBannerURL: "courseBannerURL", + courseVideoURL: nil + ) + } + + func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails { + return CourseDetails( + courseID: "courseID", + org: "Organization", + courseTitle: "Course title", + courseDescription: "Course description", + courseStart: Date(iso8601: "2021-05-26T12:13:14Z"), + courseEnd: Date(iso8601: "2022-05-26T12:13:14Z"), + enrollmentStart: nil, + enrollmentEnd: nil, + isEnrolled: false, + overviewHTML: "Course description

Lorem ipsum", + courseBannerURL: "courseBannerURL", + courseVideoURL: nil + ) + } + + public func enrollToCourse(courseID: String) async throws -> Bool { + return true + } + func getDiscovery(page: Int) async throws -> [CourseItem] { var models: [CourseItem] = [] for i in 0...10 { diff --git a/Course/Course/Domain/Model/CourseDetails.swift b/Discovery/Discovery/Data/Model/CourseDetails.swift similarity index 100% rename from Course/Course/Domain/Model/CourseDetails.swift rename to Discovery/Discovery/Data/Model/CourseDetails.swift diff --git a/Course/Course/Data/Model/Data_CourseDetailsResponse.swift b/Discovery/Discovery/Data/Model/Data_CourseDetailsResponse.swift similarity index 100% rename from Course/Course/Data/Model/Data_CourseDetailsResponse.swift rename to Discovery/Discovery/Data/Model/Data_CourseDetailsResponse.swift diff --git a/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift b/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift index 35bfeb384..848e31d85 100644 --- a/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift +++ b/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift @@ -12,6 +12,8 @@ import Alamofire enum DiscoveryEndpoint: EndPointType { case getDiscovery(username: String, page: Int) case searchCourses(username: String, page: Int, searchTerm: String) + case getCourseDetail(courseID: String, username: String) + case enrollToCourse(courseID: String) var path: String { switch self { @@ -19,6 +21,10 @@ enum DiscoveryEndpoint: EndPointType { return "/api/courses/v1/courses/" case .searchCourses: return "/api/courses/v1/courses/" + case .getCourseDetail(courseID: let courseID, _): + return "/api/courses/v1/courses/\(courseID)" + case .enrollToCourse(courseID: _): + return "/api/enrollment/v1/enrollment" } } @@ -26,6 +32,10 @@ enum DiscoveryEndpoint: EndPointType { switch self { case .getDiscovery, .searchCourses: return .get + case .getCourseDetail(courseID: _, username: _): + return .get + case .enrollToCourse: + return .post } } @@ -37,7 +47,6 @@ enum DiscoveryEndpoint: EndPointType { switch self { case let .getDiscovery(_, page): let params: Parameters = [ -// "username": username, "mobile": true, "permissions": ["enroll", "see_about_page", "see_in_catalog"], "page": page @@ -54,6 +63,19 @@ enum DiscoveryEndpoint: EndPointType { ] return .requestParameters(parameters: params, encoding: URLEncoding.queryString) + + case .enrollToCourse(courseID: let courseID): + let params: [String: Any] = [ + "course_details": [ + "course_id": courseID, + "email_opt_in": true + ] + ] + return .requestParameters(parameters: params, encoding: JSONEncoding.default) + + case let .getCourseDetail(_, username): + let params: [String: Encodable] = ["username": username] + return .requestParameters(parameters: params, encoding: URLEncoding.queryString) } } } diff --git a/Discovery/Discovery/Data/Persistence/DiscoveryCoreModel.xcdatamodeld/DiscoveryCoreModel.xcdatamodel/contents b/Discovery/Discovery/Data/Persistence/DiscoveryCoreModel.xcdatamodeld/DiscoveryCoreModel.xcdatamodel/contents index dc2f9ce96..154df9ca8 100644 --- a/Discovery/Discovery/Data/Persistence/DiscoveryCoreModel.xcdatamodeld/DiscoveryCoreModel.xcdatamodel/contents +++ b/Discovery/Discovery/Data/Persistence/DiscoveryCoreModel.xcdatamodeld/DiscoveryCoreModel.xcdatamodel/contents @@ -1,5 +1,23 @@ - + + + + + + + + + + + + + + + + + + + diff --git a/Discovery/Discovery/Data/Persistence/DiscoveryPersistenceProtocol.swift b/Discovery/Discovery/Data/Persistence/DiscoveryPersistenceProtocol.swift index a18338e3b..1c8b3fd6c 100644 --- a/Discovery/Discovery/Data/Persistence/DiscoveryPersistenceProtocol.swift +++ b/Discovery/Discovery/Data/Persistence/DiscoveryPersistenceProtocol.swift @@ -11,6 +11,8 @@ import Core public protocol DiscoveryPersistenceProtocol { func loadDiscovery() throws -> [CourseItem] func saveDiscovery(items: [CourseItem]) + func loadCourseDetails(courseID: String) throws -> CourseDetails + func saveCourseDetails(course: CourseDetails) } public final class DiscoveryBundle { diff --git a/Discovery/Discovery/Domain/DiscoveryInteractor.swift b/Discovery/Discovery/Domain/DiscoveryInteractor.swift index 1f2451d2b..a0bffe3ca 100644 --- a/Discovery/Discovery/Domain/DiscoveryInteractor.swift +++ b/Discovery/Discovery/Domain/DiscoveryInteractor.swift @@ -13,6 +13,9 @@ public protocol DiscoveryInteractorProtocol { func discovery(page: Int) async throws -> [CourseItem] func discoveryOffline() throws -> [CourseItem] func search(page: Int, searchTerm: String) async throws -> [CourseItem] + func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails + func getCourseDetails(courseID: String) async throws -> CourseDetails + func enrollToCourse(courseID: String) async throws -> Bool } public class DiscoveryInteractor: DiscoveryInteractorProtocol { @@ -34,6 +37,18 @@ public class DiscoveryInteractor: DiscoveryInteractorProtocol { public func discoveryOffline() throws -> [CourseItem] { return try repository.getDiscoveryOffline() } + + public func getCourseDetails(courseID: String) async throws -> CourseDetails { + return try await repository.getCourseDetails(courseID: courseID) + } + + public func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails { + return try await repository.getLoadedCourseDetails(courseID: courseID) + } + + public func enrollToCourse(courseID: String) async throws -> Bool { + return try await repository.enrollToCourse(courseID: courseID) + } } // Mark - For testing and SwiftUI preview diff --git a/Discovery/Discovery/Presentation/DiscoveryAnalytics.swift b/Discovery/Discovery/Presentation/DiscoveryAnalytics.swift index f86ef6341..bcb8190fc 100644 --- a/Discovery/Discovery/Presentation/DiscoveryAnalytics.swift +++ b/Discovery/Discovery/Presentation/DiscoveryAnalytics.swift @@ -12,6 +12,9 @@ public protocol DiscoveryAnalytics { func discoverySearchBarClicked() func discoveryCoursesSearch(label: String, coursesCount: Int) func discoveryCourseClicked(courseID: String, courseName: String) + func viewCourseClicked(courseId: String, courseName: String) + func courseEnrollClicked(courseId: String, courseName: String) + func courseEnrollSuccess(courseId: String, courseName: String) } #if DEBUG @@ -19,5 +22,8 @@ class DiscoveryAnalyticsMock: DiscoveryAnalytics { public func discoverySearchBarClicked() {} public func discoveryCoursesSearch(label: String, coursesCount: Int) {} public func discoveryCourseClicked(courseID: String, courseName: String) {} + public func viewCourseClicked(courseId: String, courseName: String) {} + public func courseEnrollClicked(courseId: String, courseName: String) {} + public func courseEnrollSuccess(courseId: String, courseName: String) {} } #endif diff --git a/Discovery/Discovery/Presentation/DiscoveryRouter.swift b/Discovery/Discovery/Presentation/DiscoveryRouter.swift index cea10bb50..c8fd80b29 100644 --- a/Discovery/Discovery/Presentation/DiscoveryRouter.swift +++ b/Discovery/Discovery/Presentation/DiscoveryRouter.swift @@ -14,6 +14,15 @@ public protocol DiscoveryRouter: BaseRouter { func showUpdateRequiredView(showAccountLink: Bool) func showUpdateRecomendedView() func showDiscoverySearch(searchQuery: String?) + func showCourseScreens( + courseID: String, + isActive: Bool?, + courseStart: Date?, + courseEnd: Date?, + enrollmentStart: Date?, + enrollmentEnd: Date?, + title: String + ) } // Mark - For testing and SwiftUI preview @@ -27,5 +36,14 @@ public class DiscoveryRouterMock: BaseRouterMock, DiscoveryRouter { public func showUpdateRequiredView(showAccountLink: Bool) {} public func showUpdateRecomendedView() {} public func showDiscoverySearch(searchQuery: String? = nil) {} + public func showCourseScreens( + courseID: String, + isActive: Bool?, + courseStart: Date?, + courseEnd: Date?, + enrollmentStart: Date?, + enrollmentEnd: Date?, + title: String + ) {} } #endif diff --git a/Course/Course/Presentation/Details/CourseDetailsView.swift b/Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsView.swift similarity index 97% rename from Course/Course/Presentation/Details/CourseDetailsView.swift rename to Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsView.swift index d6aa06db8..42e43fd3c 100644 --- a/Course/Course/Presentation/Details/CourseDetailsView.swift +++ b/Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsView.swift @@ -165,7 +165,7 @@ public struct CourseDetailsView: View { }.padding(.top, 8) .navigationBarHidden(false) .navigationBarBackButtonHidden(false) - .navigationTitle(CourseLocalization.Details.title) + .navigationTitle(DiscoveryLocalization.Details.title) .onReceive(NotificationCenter .Publisher(center: .default, @@ -219,7 +219,7 @@ private struct CourseStateView: View { var body: some View { switch viewModel.courseState() { case .enrollOpen: - StyledButton(CourseLocalization.Details.enrollNow, action: { + StyledButton(DiscoveryLocalization.Details.enrollNow, action: { if !viewModel.userloggedIn { viewModel.router.showRegisterScreen( sourceScreen: .courseDetail( @@ -234,13 +234,13 @@ private struct CourseStateView: View { }) .padding(16) case .enrollClose: - Text(CourseLocalization.Details.enrollmentDateIsOver) + Text(DiscoveryLocalization.Details.enrollmentDateIsOver) .multilineTextAlignment(.center) .font(Theme.Fonts.titleSmall) .cardStyle() .padding(.vertical, 24) case .alreadyEnrolled: - StyledButton(CourseLocalization.Details.viewCourse, action: { + StyledButton(DiscoveryLocalization.Details.viewCourse, action: { if !viewModel.userloggedIn { viewModel.router.showRegisterScreen( sourceScreen: .courseDetail( @@ -363,9 +363,9 @@ private struct CourseBannerView: View { struct CourseDetailsView_Previews: PreviewProvider { static var previews: some View { let vm = CourseDetailsViewModel( - interactor: CourseInteractor.mock, - router: CourseRouterMock(), - analytics: CourseAnalyticsMock(), + interactor: DiscoveryInteractor.mock, + router: DiscoveryRouterMock(), + analytics: DiscoveryAnalyticsMock(), config: ConfigMock(), cssInjector: CSSInjectorMock(), connectivity: Connectivity(), diff --git a/Course/Course/Presentation/Details/CourseDetailsViewModel.swift b/Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsViewModel.swift similarity index 94% rename from Course/Course/Presentation/Details/CourseDetailsViewModel.swift rename to Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsViewModel.swift index 3524e3d78..eafab2542 100644 --- a/Course/Course/Presentation/Details/CourseDetailsViewModel.swift +++ b/Discovery/Discovery/Presentation/NativeDiscovery/CourseDetailsViewModel.swift @@ -29,9 +29,9 @@ public class CourseDetailsViewModel: ObservableObject { } } - private let interactor: CourseInteractorProtocol - private let analytics: CourseAnalytics - let router: CourseRouter + private let interactor: DiscoveryInteractorProtocol + private let analytics: DiscoveryAnalytics + let router: DiscoveryRouter let config: ConfigProtocol let cssInjector: CSSInjector let connectivity: ConnectivityProtocol @@ -42,9 +42,9 @@ public class CourseDetailsViewModel: ObservableObject { } public init( - interactor: CourseInteractorProtocol, - router: CourseRouter, - analytics: CourseAnalytics, + interactor: DiscoveryInteractorProtocol, + router: DiscoveryRouter, + analytics: DiscoveryAnalytics, config: ConfigProtocol, cssInjector: CSSInjector, connectivity: ConnectivityProtocol, diff --git a/Discovery/Discovery/Presentation/DiscoveryView.swift b/Discovery/Discovery/Presentation/NativeDiscovery/DiscoveryView.swift similarity index 100% rename from Discovery/Discovery/Presentation/DiscoveryView.swift rename to Discovery/Discovery/Presentation/NativeDiscovery/DiscoveryView.swift diff --git a/Discovery/Discovery/Presentation/DiscoveryViewModel.swift b/Discovery/Discovery/Presentation/NativeDiscovery/DiscoveryViewModel.swift similarity index 100% rename from Discovery/Discovery/Presentation/DiscoveryViewModel.swift rename to Discovery/Discovery/Presentation/NativeDiscovery/DiscoveryViewModel.swift diff --git a/Discovery/Discovery/Presentation/SearchView.swift b/Discovery/Discovery/Presentation/NativeDiscovery/SearchView.swift similarity index 100% rename from Discovery/Discovery/Presentation/SearchView.swift rename to Discovery/Discovery/Presentation/NativeDiscovery/SearchView.swift diff --git a/Discovery/Discovery/Presentation/SearchViewModel.swift b/Discovery/Discovery/Presentation/NativeDiscovery/SearchViewModel.swift similarity index 100% rename from Discovery/Discovery/Presentation/SearchViewModel.swift rename to Discovery/Discovery/Presentation/NativeDiscovery/SearchViewModel.swift diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift index 8267ead70..d68e8a83d 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift @@ -9,7 +9,6 @@ import Foundation import Core import WebKit import SwiftUI -import Course import Swinject public class DiscoveryWebviewModel: ObservableObject { @@ -27,7 +26,7 @@ public class DiscoveryWebviewModel: ObservableObject { let router: DiscoveryRouter let config: ConfigProtocol let connectivity: ConnectivityProtocol - private let interactor: CourseInteractorProtocol + private let interactor: DiscoveryInteractorProtocol private let analytics: DiscoveryAnalytics var request: URLRequest? private let storage: CoreStorage @@ -40,7 +39,7 @@ public class DiscoveryWebviewModel: ObservableObject { public init( router: DiscoveryRouter, config: ConfigProtocol, - interactor: CourseInteractorProtocol, + interactor: DiscoveryInteractorProtocol, connectivity: ConnectivityProtocol, analytics: DiscoveryAnalytics, storage: CoreStorage, @@ -69,7 +68,9 @@ public class DiscoveryWebviewModel: ObservableObject { } showProgress = true - courseDetails = try await getCourseDetail(courseID: courseID) + if courseDetails == nil { + courseDetails = try await getCourseDetail(courseID: courseID) + } if courseDetails?.isEnrolled ?? false || courseState == .alreadyEnrolled { showProgress = false @@ -77,11 +78,9 @@ public class DiscoveryWebviewModel: ObservableObject { return } - let courseAnalytics = Container.shared.resolve(CourseAnalytics.self) - - courseAnalytics?.courseEnrollClicked(courseId: courseID, courseName: courseDetails?.courseTitle ?? "") + analytics.courseEnrollClicked(courseId: courseID, courseName: courseDetails?.courseTitle ?? "") _ = try await interactor.enrollToCourse(courseID: courseID) - courseAnalytics?.courseEnrollSuccess(courseId: courseID, courseName: courseDetails?.courseTitle ?? "") + analytics.courseEnrollSuccess(courseId: courseID, courseName: courseDetails?.courseTitle ?? "") courseDetails?.isEnrolled = true showProgress = false NotificationCenter.default.post(name: .onCourseEnrolled, object: courseID) @@ -219,9 +218,9 @@ extension DiscoveryWebviewModel: WebViewNavigationDelegate { } @discardableResult private func showCourseDetails() -> Bool { - guard let courseRouter = Container.shared.resolve(CourseRouter.self), - let courseDetails = courseDetails else { return false } - courseRouter.showCourseScreens( + guard let courseDetails = courseDetails else { return false } + + router.showCourseScreens( courseID: courseDetails.courseID, isActive: nil, courseStart: courseDetails.courseStart, diff --git a/Discovery/Discovery/SwiftGen/Strings.swift b/Discovery/Discovery/SwiftGen/Strings.swift index abe90994d..8bd26400c 100644 --- a/Discovery/Discovery/SwiftGen/Strings.swift +++ b/Discovery/Discovery/SwiftGen/Strings.swift @@ -47,6 +47,16 @@ public enum DiscoveryLocalization { /// Leaving the app public static let leavingAppTitle = DiscoveryLocalization.tr("Localizable", "ALERT.LEAVING_APP_TITLE", fallback: "Leaving the app") } + public enum Details { + /// Enroll now + public static let enrollNow = DiscoveryLocalization.tr("Localizable", "DETAILS.ENROLL_NOW", fallback: "Enroll now") + /// You cannot enroll in this course because the enrollment date is over. + public static let enrollmentDateIsOver = DiscoveryLocalization.tr("Localizable", "DETAILS.ENROLLMENT_DATE_IS_OVER", fallback: "You cannot enroll in this course because the enrollment date is over.") + /// Course details + public static let title = DiscoveryLocalization.tr("Localizable", "DETAILS.TITLE", fallback: "Course details") + /// View course + public static let viewCourse = DiscoveryLocalization.tr("Localizable", "DETAILS.VIEW_COURSE", fallback: "View course") + } public enum Header { /// Discover new public static let title1 = DiscoveryLocalization.tr("Localizable", "HEADER.TITLE_1", fallback: "Discover new") diff --git a/Discovery/Discovery/en.lproj/Localizable.strings b/Discovery/Discovery/en.lproj/Localizable.strings index bedd53a22..09bd81912 100644 --- a/Discovery/Discovery/en.lproj/Localizable.strings +++ b/Discovery/Discovery/en.lproj/Localizable.strings @@ -29,3 +29,8 @@ "ALERT.LEAVING_APP_TITLE" = "Leaving the app"; "ALERT.LEAVING_APP_MESSAGE" = "You are now leaving the app and opening a browser"; + +"DETAILS.TITLE" = "Course details"; +"DETAILS.VIEW_COURSE" = "View course"; +"DETAILS.ENROLL_NOW" = "Enroll now"; +"DETAILS.ENROLLMENT_DATE_IS_OVER" = "You cannot enroll in this course because the enrollment date is over."; diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index d3470e0ce..9f58df371 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -113,7 +113,7 @@ class ScreenAssembly: Assembly { DiscoveryWebviewModel( router: r.resolve(DiscoveryRouter.self)!, config: r.resolve(ConfigProtocol.self)!, - interactor: r.resolve(CourseInteractorProtocol.self)!, + interactor: r.resolve(DiscoveryInteractorProtocol.self)!, connectivity: r.resolve(ConnectivityProtocol.self)!, analytics: r.resolve(DiscoveryAnalytics.self)!, storage: r.resolve(CoreStorage.self)!, @@ -227,9 +227,9 @@ class ScreenAssembly: Assembly { } container.register(CourseDetailsViewModel.self) { r in CourseDetailsViewModel( - interactor: r.resolve(CourseInteractorProtocol.self)!, - router: r.resolve(CourseRouter.self)!, - analytics: r.resolve(CourseAnalytics.self)!, + interactor: r.resolve(DiscoveryInteractorProtocol.self)!, + router: r.resolve(DiscoveryRouter.self)!, + analytics: r.resolve(DiscoveryAnalytics.self)!, config: r.resolve(ConfigProtocol.self)!, cssInjector: r.resolve(CSSInjector.self)!, connectivity: r.resolve(ConnectivityProtocol.self)!, diff --git a/OpenEdX/Data/CoursePersistence.swift b/OpenEdX/Data/CoursePersistence.swift index 6c7f5d83e..077301130 100644 --- a/OpenEdX/Data/CoursePersistence.swift +++ b/OpenEdX/Data/CoursePersistence.swift @@ -18,47 +18,6 @@ public class CoursePersistence: CoursePersistenceProtocol { self.context = context } - public func loadCourseDetails(courseID: String) throws -> CourseDetails { - let request = CDCourseDetails.fetchRequest() - request.predicate = NSPredicate(format: "courseID = %@", courseID) - guard let courseDetails = try? context.fetch(request).first else { throw NoCachedDataError() } - return CourseDetails(courseID: courseDetails.courseID ?? "", - org: courseDetails.org ?? "", - courseTitle: courseDetails.courseTitle ?? "", - courseDescription: courseDetails.courseDescription ?? "", - courseStart: courseDetails.courseStart, - courseEnd: courseDetails.courseEnd, - enrollmentStart: courseDetails.enrollmentStart, - enrollmentEnd: courseDetails.enrollmentEnd, - isEnrolled: courseDetails.isEnrolled, - overviewHTML: courseDetails.overviewHTML ?? "", - courseBannerURL: courseDetails.courseBannerURL ?? "", - courseVideoURL: nil) - } - - public func saveCourseDetails(course: CourseDetails) { - context.performAndWait { - let newCourseDetails = CDCourseDetails(context: self.context) - newCourseDetails.courseID = course.courseID - newCourseDetails.org = course.org - newCourseDetails.courseTitle = course.courseTitle - newCourseDetails.courseDescription = course.courseDescription - newCourseDetails.courseStart = course.courseStart - newCourseDetails.courseEnd = course.courseEnd - newCourseDetails.enrollmentStart = course.enrollmentStart - newCourseDetails.enrollmentEnd = course.enrollmentEnd - newCourseDetails.isEnrolled = course.isEnrolled - newCourseDetails.overviewHTML = course.overviewHTML - newCourseDetails.courseBannerURL = course.courseBannerURL - - do { - try context.save() - } catch { - print("⛔️⛔️⛔️⛔️⛔️", error) - } - } - } - public func loadEnrollments() throws -> [CourseItem] { let result = try? context.fetch(CDCourseItem.fetchRequest()) .map { diff --git a/OpenEdX/Data/DiscoveryPersistence.swift b/OpenEdX/Data/DiscoveryPersistence.swift index 7547d0c37..b56bfa20e 100644 --- a/OpenEdX/Data/DiscoveryPersistence.swift +++ b/OpenEdX/Data/DiscoveryPersistence.swift @@ -66,4 +66,45 @@ public class DiscoveryPersistence: DiscoveryPersistenceProtocol { } } } + + public func loadCourseDetails(courseID: String) throws -> CourseDetails { + let request = CDCourseDetails.fetchRequest() + request.predicate = NSPredicate(format: "courseID = %@", courseID) + guard let courseDetails = try? context.fetch(request).first else { throw NoCachedDataError() } + return CourseDetails(courseID: courseDetails.courseID ?? "", + org: courseDetails.org ?? "", + courseTitle: courseDetails.courseTitle ?? "", + courseDescription: courseDetails.courseDescription ?? "", + courseStart: courseDetails.courseStart, + courseEnd: courseDetails.courseEnd, + enrollmentStart: courseDetails.enrollmentStart, + enrollmentEnd: courseDetails.enrollmentEnd, + isEnrolled: courseDetails.isEnrolled, + overviewHTML: courseDetails.overviewHTML ?? "", + courseBannerURL: courseDetails.courseBannerURL ?? "", + courseVideoURL: nil) + } + + public func saveCourseDetails(course: CourseDetails) { + context.performAndWait { + let newCourseDetails = CDCourseDetails(context: self.context) + newCourseDetails.courseID = course.courseID + newCourseDetails.org = course.org + newCourseDetails.courseTitle = course.courseTitle + newCourseDetails.courseDescription = course.courseDescription + newCourseDetails.courseStart = course.courseStart + newCourseDetails.courseEnd = course.courseEnd + newCourseDetails.enrollmentStart = course.enrollmentStart + newCourseDetails.enrollmentEnd = course.enrollmentEnd + newCourseDetails.isEnrolled = course.isEnrolled + newCourseDetails.overviewHTML = course.overviewHTML + newCourseDetails.courseBannerURL = course.courseBannerURL + + do { + try context.save() + } catch { + print("⛔️⛔️⛔️⛔️⛔️", error) + } + } + } } diff --git a/OpenEdX/View/MainScreenView.swift b/OpenEdX/View/MainScreenView.swift index 13d524081..9bd8588c3 100644 --- a/OpenEdX/View/MainScreenView.swift +++ b/OpenEdX/View/MainScreenView.swift @@ -41,32 +41,34 @@ struct MainScreenView: View { var body: some View { TabView(selection: $selection) { - ZStack { - let config = Container.shared.resolve(ConfigProtocol.self) - if config?.discovery.type == .native { - DiscoveryView( - viewModel: Container.shared.resolve(DiscoveryViewModel.self)!, - router: Container.shared.resolve(DiscoveryRouter.self)!, - sourceScreen: viewModel.sourceScreen - ) - } else if config?.discovery.type == .webview { - DiscoveryWebview( - viewModel: Container.shared.resolve( - DiscoveryWebviewModel.self, - argument: viewModel.sourceScreen)!, - router: Container.shared.resolve(DiscoveryRouter.self)! - ) + let config = Container.shared.resolve(ConfigProtocol.self) + if config?.discovery.enabled ?? false { + ZStack { + if config?.discovery.type == .native { + DiscoveryView( + viewModel: Container.shared.resolve(DiscoveryViewModel.self)!, + router: Container.shared.resolve(DiscoveryRouter.self)!, + sourceScreen: viewModel.sourceScreen + ) + } else if config?.discovery.type == .webview { + DiscoveryWebview( + viewModel: Container.shared.resolve( + DiscoveryWebviewModel.self, + argument: viewModel.sourceScreen)!, + router: Container.shared.resolve(DiscoveryRouter.self)! + ) + } + + if updateAvaliable { + UpdateNotificationView(config: viewModel.config) + } } - - if updateAvaliable { - UpdateNotificationView(config: viewModel.config) + .tabItem { + CoreAssets.discovery.swiftUIImage.renderingMode(.template) + Text(CoreLocalization.Mainscreen.discovery) } + .tag(MainTab.discovery) } - .tabItem { - CoreAssets.discovery.swiftUIImage.renderingMode(.template) - Text(CoreLocalization.Mainscreen.discovery) - } - .tag(MainTab.discovery) ZStack { DashboardView( From 6666cb8663f67f1fc782731520979c0daf603f45 Mon Sep 17 00:00:00 2001 From: saeedbashir Date: Wed, 10 Jan 2024 10:00:52 +0500 Subject: [PATCH 04/14] chore: regenerating tests --- Course/CourseTests/CourseMock.generated.swift | 180 ------------------ .../DiscoveryMock.generated.swift | 180 ++++++++++++++++++ 2 files changed, 180 insertions(+), 180 deletions(-) diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index d536c8379..af021d83a 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -1138,24 +1138,6 @@ open class CourseAnalyticsMock: CourseAnalytics, Mock { - open func courseEnrollClicked(courseId: String, courseName: String) { - addInvocation(.m_courseEnrollClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) - let perform = methodPerformValue(.m_courseEnrollClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) as? (String, String) -> Void - perform?(`courseId`, `courseName`) - } - - open func courseEnrollSuccess(courseId: String, courseName: String) { - addInvocation(.m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) - let perform = methodPerformValue(.m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) as? (String, String) -> Void - perform?(`courseId`, `courseName`) - } - - open func viewCourseClicked(courseId: String, courseName: String) { - addInvocation(.m_viewCourseClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) - let perform = methodPerformValue(.m_viewCourseClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) as? (String, String) -> Void - perform?(`courseId`, `courseName`) - } - open func resumeCourseTapped(courseId: String, courseName: String, blockId: String) { addInvocation(.m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId(Parameter.value(`courseId`), Parameter.value(`courseName`), Parameter.value(`blockId`))) let perform = methodPerformValue(.m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId(Parameter.value(`courseId`), Parameter.value(`courseName`), Parameter.value(`blockId`))) as? (String, String, String) -> Void @@ -1236,9 +1218,6 @@ open class CourseAnalyticsMock: CourseAnalytics, Mock { fileprivate enum MethodType { - case m_courseEnrollClicked__courseId_courseIdcourseName_courseName(Parameter, Parameter) - case m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(Parameter, Parameter) - case m_viewCourseClicked__courseId_courseIdcourseName_courseName(Parameter, Parameter) case m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId(Parameter, Parameter, Parameter) case m_sequentialClicked__courseId_courseIdcourseName_courseNameblockId_blockIdblockName_blockName(Parameter, Parameter, Parameter, Parameter) case m_verticalClicked__courseId_courseIdcourseName_courseNameblockId_blockIdblockName_blockName(Parameter, Parameter, Parameter, Parameter) @@ -1255,24 +1234,6 @@ open class CourseAnalyticsMock: CourseAnalytics, Mock { static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { - case (.m_courseEnrollClicked__courseId_courseIdcourseName_courseName(let lhsCourseid, let lhsCoursename), .m_courseEnrollClicked__courseId_courseIdcourseName_courseName(let rhsCourseid, let rhsCoursename)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) - return Matcher.ComparisonResult(results) - - case (.m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(let lhsCourseid, let lhsCoursename), .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(let rhsCourseid, let rhsCoursename)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) - return Matcher.ComparisonResult(results) - - case (.m_viewCourseClicked__courseId_courseIdcourseName_courseName(let lhsCourseid, let lhsCoursename), .m_viewCourseClicked__courseId_courseIdcourseName_courseName(let rhsCourseid, let rhsCoursename)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) - return Matcher.ComparisonResult(results) - case (.m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId(let lhsCourseid, let lhsCoursename, let lhsBlockid), .m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId(let rhsCourseid, let rhsCoursename, let rhsBlockid)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) @@ -1369,9 +1330,6 @@ open class CourseAnalyticsMock: CourseAnalytics, Mock { func intValue() -> Int { switch self { - case let .m_courseEnrollClicked__courseId_courseIdcourseName_courseName(p0, p1): return p0.intValue + p1.intValue - case let .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(p0, p1): return p0.intValue + p1.intValue - case let .m_viewCourseClicked__courseId_courseIdcourseName_courseName(p0, p1): return p0.intValue + p1.intValue case let .m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue case let .m_sequentialClicked__courseId_courseIdcourseName_courseNameblockId_blockIdblockName_blockName(p0, p1, p2, p3): return p0.intValue + p1.intValue + p2.intValue + p3.intValue case let .m_verticalClicked__courseId_courseIdcourseName_courseNameblockId_blockIdblockName_blockName(p0, p1, p2, p3): return p0.intValue + p1.intValue + p2.intValue + p3.intValue @@ -1389,9 +1347,6 @@ open class CourseAnalyticsMock: CourseAnalytics, Mock { } func assertionName() -> String { switch self { - case .m_courseEnrollClicked__courseId_courseIdcourseName_courseName: return ".courseEnrollClicked(courseId:courseName:)" - case .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName: return ".courseEnrollSuccess(courseId:courseName:)" - case .m_viewCourseClicked__courseId_courseIdcourseName_courseName: return ".viewCourseClicked(courseId:courseName:)" case .m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId: return ".resumeCourseTapped(courseId:courseName:blockId:)" case .m_sequentialClicked__courseId_courseIdcourseName_courseNameblockId_blockIdblockName_blockName: return ".sequentialClicked(courseId:courseName:blockId:blockName:)" case .m_verticalClicked__courseId_courseIdcourseName_courseNameblockId_blockIdblockName_blockName: return ".verticalClicked(courseId:courseName:blockId:blockName:)" @@ -1423,9 +1378,6 @@ open class CourseAnalyticsMock: CourseAnalytics, Mock { public struct Verify { fileprivate var method: MethodType - public static func courseEnrollClicked(courseId: Parameter, courseName: Parameter) -> Verify { return Verify(method: .m_courseEnrollClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`))} - public static func courseEnrollSuccess(courseId: Parameter, courseName: Parameter) -> Verify { return Verify(method: .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(`courseId`, `courseName`))} - public static func viewCourseClicked(courseId: Parameter, courseName: Parameter) -> Verify { return Verify(method: .m_viewCourseClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`))} public static func resumeCourseTapped(courseId: Parameter, courseName: Parameter, blockId: Parameter) -> Verify { return Verify(method: .m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId(`courseId`, `courseName`, `blockId`))} public static func sequentialClicked(courseId: Parameter, courseName: Parameter, blockId: Parameter, blockName: Parameter) -> Verify { return Verify(method: .m_sequentialClicked__courseId_courseIdcourseName_courseNameblockId_blockIdblockName_blockName(`courseId`, `courseName`, `blockId`, `blockName`))} public static func verticalClicked(courseId: Parameter, courseName: Parameter, blockId: Parameter, blockName: Parameter) -> Verify { return Verify(method: .m_verticalClicked__courseId_courseIdcourseName_courseNameblockId_blockIdblockName_blockName(`courseId`, `courseName`, `blockId`, `blockName`))} @@ -1445,15 +1397,6 @@ open class CourseAnalyticsMock: CourseAnalytics, Mock { fileprivate var method: MethodType var performs: Any - public static func courseEnrollClicked(courseId: Parameter, courseName: Parameter, perform: @escaping (String, String) -> Void) -> Perform { - return Perform(method: .m_courseEnrollClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`), performs: perform) - } - public static func courseEnrollSuccess(courseId: Parameter, courseName: Parameter, perform: @escaping (String, String) -> Void) -> Perform { - return Perform(method: .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(`courseId`, `courseName`), performs: perform) - } - public static func viewCourseClicked(courseId: Parameter, courseName: Parameter, perform: @escaping (String, String) -> Void) -> Perform { - return Perform(method: .m_viewCourseClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`), performs: perform) - } public static func resumeCourseTapped(courseId: Parameter, courseName: Parameter, blockId: Parameter, perform: @escaping (String, String, String) -> Void) -> Perform { return Perform(method: .m_resumeCourseTapped__courseId_courseIdcourseName_courseNameblockId_blockId(`courseId`, `courseName`, `blockId`), performs: perform) } @@ -1612,22 +1555,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { - open func getCourseDetails(courseID: String) throws -> CourseDetails { - addInvocation(.m_getCourseDetails__courseID_courseID(Parameter.value(`courseID`))) - let perform = methodPerformValue(.m_getCourseDetails__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void - perform?(`courseID`) - var __value: CourseDetails - do { - __value = try methodReturnValue(.m_getCourseDetails__courseID_courseID(Parameter.value(`courseID`))).casted() - } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for getCourseDetails(courseID: String). Use given") - Failure("Stub return value not specified for getCourseDetails(courseID: String). Use given") - } catch { - throw error - } - return __value - } - open func getCourseBlocks(courseID: String) throws -> CourseStructure { addInvocation(.m_getCourseBlocks__courseID_courseID(Parameter.value(`courseID`))) let perform = methodPerformValue(.m_getCourseBlocks__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void @@ -1658,22 +1585,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { return __value } - open func getLoadedCourseDetails(courseID: String) throws -> CourseDetails { - addInvocation(.m_getLoadedCourseDetails__courseID_courseID(Parameter.value(`courseID`))) - let perform = methodPerformValue(.m_getLoadedCourseDetails__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void - perform?(`courseID`) - var __value: CourseDetails - do { - __value = try methodReturnValue(.m_getLoadedCourseDetails__courseID_courseID(Parameter.value(`courseID`))).casted() - } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for getLoadedCourseDetails(courseID: String). Use given") - Failure("Stub return value not specified for getLoadedCourseDetails(courseID: String). Use given") - } catch { - throw error - } - return __value - } - open func getLoadedCourseBlocks(courseID: String) throws -> CourseStructure { addInvocation(.m_getLoadedCourseBlocks__courseID_courseID(Parameter.value(`courseID`))) let perform = methodPerformValue(.m_getLoadedCourseBlocks__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void @@ -1690,22 +1601,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { return __value } - open func enrollToCourse(courseID: String) throws -> Bool { - addInvocation(.m_enrollToCourse__courseID_courseID(Parameter.value(`courseID`))) - let perform = methodPerformValue(.m_enrollToCourse__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void - perform?(`courseID`) - var __value: Bool - do { - __value = try methodReturnValue(.m_enrollToCourse__courseID_courseID(Parameter.value(`courseID`))).casted() - } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for enrollToCourse(courseID: String). Use given") - Failure("Stub return value not specified for enrollToCourse(courseID: String). Use given") - } catch { - throw error - } - return __value - } - open func blockCompletionRequest(courseID: String, blockID: String) throws { addInvocation(.m_blockCompletionRequest__courseID_courseIDblockID_blockID(Parameter.value(`courseID`), Parameter.value(`blockID`))) let perform = methodPerformValue(.m_blockCompletionRequest__courseID_courseIDblockID_blockID(Parameter.value(`courseID`), Parameter.value(`blockID`))) as? (String, String) -> Void @@ -1800,12 +1695,9 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { fileprivate enum MethodType { - case m_getCourseDetails__courseID_courseID(Parameter) case m_getCourseBlocks__courseID_courseID(Parameter) case m_getCourseVideoBlocks__fullStructure_fullStructure(Parameter) - case m_getLoadedCourseDetails__courseID_courseID(Parameter) case m_getLoadedCourseBlocks__courseID_courseID(Parameter) - case m_enrollToCourse__courseID_courseID(Parameter) case m_blockCompletionRequest__courseID_courseIDblockID_blockID(Parameter, Parameter) case m_getHandouts__courseID_courseID(Parameter) case m_getUpdates__courseID_courseID(Parameter) @@ -1815,11 +1707,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { - case (.m_getCourseDetails__courseID_courseID(let lhsCourseid), .m_getCourseDetails__courseID_courseID(let rhsCourseid)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) - return Matcher.ComparisonResult(results) - case (.m_getCourseBlocks__courseID_courseID(let lhsCourseid), .m_getCourseBlocks__courseID_courseID(let rhsCourseid)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) @@ -1830,21 +1717,11 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFullstructure, rhs: rhsFullstructure, with: matcher), lhsFullstructure, rhsFullstructure, "fullStructure")) return Matcher.ComparisonResult(results) - case (.m_getLoadedCourseDetails__courseID_courseID(let lhsCourseid), .m_getLoadedCourseDetails__courseID_courseID(let rhsCourseid)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) - return Matcher.ComparisonResult(results) - case (.m_getLoadedCourseBlocks__courseID_courseID(let lhsCourseid), .m_getLoadedCourseBlocks__courseID_courseID(let rhsCourseid)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) - case (.m_enrollToCourse__courseID_courseID(let lhsCourseid), .m_enrollToCourse__courseID_courseID(let rhsCourseid)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) - return Matcher.ComparisonResult(results) - case (.m_blockCompletionRequest__courseID_courseIDblockID_blockID(let lhsCourseid, let lhsBlockid), .m_blockCompletionRequest__courseID_courseIDblockID_blockID(let rhsCourseid, let rhsBlockid)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) @@ -1882,12 +1759,9 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { func intValue() -> Int { switch self { - case let .m_getCourseDetails__courseID_courseID(p0): return p0.intValue case let .m_getCourseBlocks__courseID_courseID(p0): return p0.intValue case let .m_getCourseVideoBlocks__fullStructure_fullStructure(p0): return p0.intValue - case let .m_getLoadedCourseDetails__courseID_courseID(p0): return p0.intValue case let .m_getLoadedCourseBlocks__courseID_courseID(p0): return p0.intValue - case let .m_enrollToCourse__courseID_courseID(p0): return p0.intValue case let .m_blockCompletionRequest__courseID_courseIDblockID_blockID(p0, p1): return p0.intValue + p1.intValue case let .m_getHandouts__courseID_courseID(p0): return p0.intValue case let .m_getUpdates__courseID_courseID(p0): return p0.intValue @@ -1898,12 +1772,9 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { } func assertionName() -> String { switch self { - case .m_getCourseDetails__courseID_courseID: return ".getCourseDetails(courseID:)" case .m_getCourseBlocks__courseID_courseID: return ".getCourseBlocks(courseID:)" case .m_getCourseVideoBlocks__fullStructure_fullStructure: return ".getCourseVideoBlocks(fullStructure:)" - case .m_getLoadedCourseDetails__courseID_courseID: return ".getLoadedCourseDetails(courseID:)" case .m_getLoadedCourseBlocks__courseID_courseID: return ".getLoadedCourseBlocks(courseID:)" - case .m_enrollToCourse__courseID_courseID: return ".enrollToCourse(courseID:)" case .m_blockCompletionRequest__courseID_courseIDblockID_blockID: return ".blockCompletionRequest(courseID:blockID:)" case .m_getHandouts__courseID_courseID: return ".getHandouts(courseID:)" case .m_getUpdates__courseID_courseID: return ".getUpdates(courseID:)" @@ -1923,24 +1794,15 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { } - public static func getCourseDetails(courseID: Parameter, willReturn: CourseDetails...) -> MethodStub { - return Given(method: .m_getCourseDetails__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) - } public static func getCourseBlocks(courseID: Parameter, willReturn: CourseStructure...) -> MethodStub { return Given(method: .m_getCourseBlocks__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func getCourseVideoBlocks(fullStructure: Parameter, willReturn: CourseStructure...) -> MethodStub { return Given(method: .m_getCourseVideoBlocks__fullStructure_fullStructure(`fullStructure`), products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func getLoadedCourseDetails(courseID: Parameter, willReturn: CourseDetails...) -> MethodStub { - return Given(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) - } public static func getLoadedCourseBlocks(courseID: Parameter, willReturn: CourseStructure...) -> MethodStub { return Given(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func enrollToCourse(courseID: Parameter, willReturn: Bool...) -> MethodStub { - return Given(method: .m_enrollToCourse__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) - } public static func getHandouts(courseID: Parameter, willReturn: String?...) -> MethodStub { return Given(method: .m_getHandouts__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } @@ -1963,16 +1825,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { willProduce(stubber) return given } - public static func getCourseDetails(courseID: Parameter, willThrow: Error...) -> MethodStub { - return Given(method: .m_getCourseDetails__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) - } - public static func getCourseDetails(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { - let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_getCourseDetails__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() - let stubber = given.stubThrows(for: (CourseDetails).self) - willProduce(stubber) - return given - } public static func getCourseBlocks(courseID: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_getCourseBlocks__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -1983,16 +1835,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { willProduce(stubber) return given } - public static func getLoadedCourseDetails(courseID: Parameter, willThrow: Error...) -> MethodStub { - return Given(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) - } - public static func getLoadedCourseDetails(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { - let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() - let stubber = given.stubThrows(for: (CourseDetails).self) - willProduce(stubber) - return given - } public static func getLoadedCourseBlocks(courseID: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -2003,16 +1845,6 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { willProduce(stubber) return given } - public static func enrollToCourse(courseID: Parameter, willThrow: Error...) -> MethodStub { - return Given(method: .m_enrollToCourse__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) - } - public static func enrollToCourse(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { - let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_enrollToCourse__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() - let stubber = given.stubThrows(for: (Bool).self) - willProduce(stubber) - return given - } public static func blockCompletionRequest(courseID: Parameter, blockID: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_blockCompletionRequest__courseID_courseIDblockID_blockID(`courseID`, `blockID`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -2078,12 +1910,9 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { public struct Verify { fileprivate var method: MethodType - public static func getCourseDetails(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseDetails__courseID_courseID(`courseID`))} public static func getCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseBlocks__courseID_courseID(`courseID`))} public static func getCourseVideoBlocks(fullStructure: Parameter) -> Verify { return Verify(method: .m_getCourseVideoBlocks__fullStructure_fullStructure(`fullStructure`))} - public static func getLoadedCourseDetails(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`))} public static func getLoadedCourseBlocks(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`))} - public static func enrollToCourse(courseID: Parameter) -> Verify { return Verify(method: .m_enrollToCourse__courseID_courseID(`courseID`))} public static func blockCompletionRequest(courseID: Parameter, blockID: Parameter) -> Verify { return Verify(method: .m_blockCompletionRequest__courseID_courseIDblockID_blockID(`courseID`, `blockID`))} public static func getHandouts(courseID: Parameter) -> Verify { return Verify(method: .m_getHandouts__courseID_courseID(`courseID`))} public static func getUpdates(courseID: Parameter) -> Verify { return Verify(method: .m_getUpdates__courseID_courseID(`courseID`))} @@ -2096,24 +1925,15 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock { fileprivate var method: MethodType var performs: Any - public static func getCourseDetails(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { - return Perform(method: .m_getCourseDetails__courseID_courseID(`courseID`), performs: perform) - } public static func getCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getCourseBlocks__courseID_courseID(`courseID`), performs: perform) } public static func getCourseVideoBlocks(fullStructure: Parameter, perform: @escaping (CourseStructure) -> Void) -> Perform { return Perform(method: .m_getCourseVideoBlocks__fullStructure_fullStructure(`fullStructure`), performs: perform) } - public static func getLoadedCourseDetails(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { - return Perform(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`), performs: perform) - } public static func getLoadedCourseBlocks(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getLoadedCourseBlocks__courseID_courseID(`courseID`), performs: perform) } - public static func enrollToCourse(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { - return Perform(method: .m_enrollToCourse__courseID_courseID(`courseID`), performs: perform) - } public static func blockCompletionRequest(courseID: Parameter, blockID: Parameter, perform: @escaping (String, String) -> Void) -> Perform { return Perform(method: .m_blockCompletionRequest__courseID_courseIDblockID_blockID(`courseID`, `blockID`), performs: perform) } diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index 72b350de5..2614d53a9 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -1156,11 +1156,32 @@ open class DiscoveryAnalyticsMock: DiscoveryAnalytics, Mock { perform?(`courseID`, `courseName`) } + open func viewCourseClicked(courseId: String, courseName: String) { + addInvocation(.m_viewCourseClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) + let perform = methodPerformValue(.m_viewCourseClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) as? (String, String) -> Void + perform?(`courseId`, `courseName`) + } + + open func courseEnrollClicked(courseId: String, courseName: String) { + addInvocation(.m_courseEnrollClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) + let perform = methodPerformValue(.m_courseEnrollClicked__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) as? (String, String) -> Void + perform?(`courseId`, `courseName`) + } + + open func courseEnrollSuccess(courseId: String, courseName: String) { + addInvocation(.m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) + let perform = methodPerformValue(.m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(Parameter.value(`courseId`), Parameter.value(`courseName`))) as? (String, String) -> Void + perform?(`courseId`, `courseName`) + } + fileprivate enum MethodType { case m_discoverySearchBarClicked case m_discoveryCoursesSearch__label_labelcoursesCount_coursesCount(Parameter, Parameter) case m_discoveryCourseClicked__courseID_courseIDcourseName_courseName(Parameter, Parameter) + case m_viewCourseClicked__courseId_courseIdcourseName_courseName(Parameter, Parameter) + case m_courseEnrollClicked__courseId_courseIdcourseName_courseName(Parameter, Parameter) + case m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(Parameter, Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -1177,6 +1198,24 @@ open class DiscoveryAnalyticsMock: DiscoveryAnalytics, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) return Matcher.ComparisonResult(results) + + case (.m_viewCourseClicked__courseId_courseIdcourseName_courseName(let lhsCourseid, let lhsCoursename), .m_viewCourseClicked__courseId_courseIdcourseName_courseName(let rhsCourseid, let rhsCoursename)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) + return Matcher.ComparisonResult(results) + + case (.m_courseEnrollClicked__courseId_courseIdcourseName_courseName(let lhsCourseid, let lhsCoursename), .m_courseEnrollClicked__courseId_courseIdcourseName_courseName(let rhsCourseid, let rhsCoursename)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) + return Matcher.ComparisonResult(results) + + case (.m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(let lhsCourseid, let lhsCoursename), .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(let rhsCourseid, let rhsCoursename)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseId")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCoursename, rhs: rhsCoursename, with: matcher), lhsCoursename, rhsCoursename, "courseName")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -1186,6 +1225,9 @@ open class DiscoveryAnalyticsMock: DiscoveryAnalytics, Mock { case .m_discoverySearchBarClicked: return 0 case let .m_discoveryCoursesSearch__label_labelcoursesCount_coursesCount(p0, p1): return p0.intValue + p1.intValue case let .m_discoveryCourseClicked__courseID_courseIDcourseName_courseName(p0, p1): return p0.intValue + p1.intValue + case let .m_viewCourseClicked__courseId_courseIdcourseName_courseName(p0, p1): return p0.intValue + p1.intValue + case let .m_courseEnrollClicked__courseId_courseIdcourseName_courseName(p0, p1): return p0.intValue + p1.intValue + case let .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(p0, p1): return p0.intValue + p1.intValue } } func assertionName() -> String { @@ -1193,6 +1235,9 @@ open class DiscoveryAnalyticsMock: DiscoveryAnalytics, Mock { case .m_discoverySearchBarClicked: return ".discoverySearchBarClicked()" case .m_discoveryCoursesSearch__label_labelcoursesCount_coursesCount: return ".discoveryCoursesSearch(label:coursesCount:)" case .m_discoveryCourseClicked__courseID_courseIDcourseName_courseName: return ".discoveryCourseClicked(courseID:courseName:)" + case .m_viewCourseClicked__courseId_courseIdcourseName_courseName: return ".viewCourseClicked(courseId:courseName:)" + case .m_courseEnrollClicked__courseId_courseIdcourseName_courseName: return ".courseEnrollClicked(courseId:courseName:)" + case .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName: return ".courseEnrollSuccess(courseId:courseName:)" } } } @@ -1214,6 +1259,9 @@ open class DiscoveryAnalyticsMock: DiscoveryAnalytics, Mock { public static func discoverySearchBarClicked() -> Verify { return Verify(method: .m_discoverySearchBarClicked)} public static func discoveryCoursesSearch(label: Parameter, coursesCount: Parameter) -> Verify { return Verify(method: .m_discoveryCoursesSearch__label_labelcoursesCount_coursesCount(`label`, `coursesCount`))} public static func discoveryCourseClicked(courseID: Parameter, courseName: Parameter) -> Verify { return Verify(method: .m_discoveryCourseClicked__courseID_courseIDcourseName_courseName(`courseID`, `courseName`))} + public static func viewCourseClicked(courseId: Parameter, courseName: Parameter) -> Verify { return Verify(method: .m_viewCourseClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`))} + public static func courseEnrollClicked(courseId: Parameter, courseName: Parameter) -> Verify { return Verify(method: .m_courseEnrollClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`))} + public static func courseEnrollSuccess(courseId: Parameter, courseName: Parameter) -> Verify { return Verify(method: .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(`courseId`, `courseName`))} } public struct Perform { @@ -1229,6 +1277,15 @@ open class DiscoveryAnalyticsMock: DiscoveryAnalytics, Mock { public static func discoveryCourseClicked(courseID: Parameter, courseName: Parameter, perform: @escaping (String, String) -> Void) -> Perform { return Perform(method: .m_discoveryCourseClicked__courseID_courseIDcourseName_courseName(`courseID`, `courseName`), performs: perform) } + public static func viewCourseClicked(courseId: Parameter, courseName: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_viewCourseClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`), performs: perform) + } + public static func courseEnrollClicked(courseId: Parameter, courseName: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_courseEnrollClicked__courseId_courseIdcourseName_courseName(`courseId`, `courseName`), performs: perform) + } + public static func courseEnrollSuccess(courseId: Parameter, courseName: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_courseEnrollSuccess__courseId_courseIdcourseName_courseName(`courseId`, `courseName`), performs: perform) + } } public func given(_ method: Given) { @@ -1396,11 +1453,62 @@ open class DiscoveryInteractorProtocolMock: DiscoveryInteractorProtocol, Mock { return __value } + open func getLoadedCourseDetails(courseID: String) throws -> CourseDetails { + addInvocation(.m_getLoadedCourseDetails__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_getLoadedCourseDetails__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + var __value: CourseDetails + do { + __value = try methodReturnValue(.m_getLoadedCourseDetails__courseID_courseID(Parameter.value(`courseID`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getLoadedCourseDetails(courseID: String). Use given") + Failure("Stub return value not specified for getLoadedCourseDetails(courseID: String). Use given") + } catch { + throw error + } + return __value + } + + open func getCourseDetails(courseID: String) throws -> CourseDetails { + addInvocation(.m_getCourseDetails__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_getCourseDetails__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + var __value: CourseDetails + do { + __value = try methodReturnValue(.m_getCourseDetails__courseID_courseID(Parameter.value(`courseID`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getCourseDetails(courseID: String). Use given") + Failure("Stub return value not specified for getCourseDetails(courseID: String). Use given") + } catch { + throw error + } + return __value + } + + open func enrollToCourse(courseID: String) throws -> Bool { + addInvocation(.m_enrollToCourse__courseID_courseID(Parameter.value(`courseID`))) + let perform = methodPerformValue(.m_enrollToCourse__courseID_courseID(Parameter.value(`courseID`))) as? (String) -> Void + perform?(`courseID`) + var __value: Bool + do { + __value = try methodReturnValue(.m_enrollToCourse__courseID_courseID(Parameter.value(`courseID`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for enrollToCourse(courseID: String). Use given") + Failure("Stub return value not specified for enrollToCourse(courseID: String). Use given") + } catch { + throw error + } + return __value + } + fileprivate enum MethodType { case m_discovery__page_page(Parameter) case m_discoveryOffline case m_search__page_pagesearchTerm_searchTerm(Parameter, Parameter) + case m_getLoadedCourseDetails__courseID_courseID(Parameter) + case m_getCourseDetails__courseID_courseID(Parameter) + case m_enrollToCourse__courseID_courseID(Parameter) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -1416,6 +1524,21 @@ open class DiscoveryInteractorProtocolMock: DiscoveryInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPage, rhs: rhsPage, with: matcher), lhsPage, rhsPage, "page")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSearchterm, rhs: rhsSearchterm, with: matcher), lhsSearchterm, rhsSearchterm, "searchTerm")) return Matcher.ComparisonResult(results) + + case (.m_getLoadedCourseDetails__courseID_courseID(let lhsCourseid), .m_getLoadedCourseDetails__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) + + case (.m_getCourseDetails__courseID_courseID(let lhsCourseid), .m_getCourseDetails__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) + + case (.m_enrollToCourse__courseID_courseID(let lhsCourseid), .m_enrollToCourse__courseID_courseID(let rhsCourseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) + return Matcher.ComparisonResult(results) default: return .none } } @@ -1425,6 +1548,9 @@ open class DiscoveryInteractorProtocolMock: DiscoveryInteractorProtocol, Mock { case let .m_discovery__page_page(p0): return p0.intValue case .m_discoveryOffline: return 0 case let .m_search__page_pagesearchTerm_searchTerm(p0, p1): return p0.intValue + p1.intValue + case let .m_getLoadedCourseDetails__courseID_courseID(p0): return p0.intValue + case let .m_getCourseDetails__courseID_courseID(p0): return p0.intValue + case let .m_enrollToCourse__courseID_courseID(p0): return p0.intValue } } func assertionName() -> String { @@ -1432,6 +1558,9 @@ open class DiscoveryInteractorProtocolMock: DiscoveryInteractorProtocol, Mock { case .m_discovery__page_page: return ".discovery(page:)" case .m_discoveryOffline: return ".discoveryOffline()" case .m_search__page_pagesearchTerm_searchTerm: return ".search(page:searchTerm:)" + case .m_getLoadedCourseDetails__courseID_courseID: return ".getLoadedCourseDetails(courseID:)" + case .m_getCourseDetails__courseID_courseID: return ".getCourseDetails(courseID:)" + case .m_enrollToCourse__courseID_courseID: return ".enrollToCourse(courseID:)" } } } @@ -1454,6 +1583,15 @@ open class DiscoveryInteractorProtocolMock: DiscoveryInteractorProtocol, Mock { public static func search(page: Parameter, searchTerm: Parameter, willReturn: [CourseItem]...) -> MethodStub { return Given(method: .m_search__page_pagesearchTerm_searchTerm(`page`, `searchTerm`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + public static func getLoadedCourseDetails(courseID: Parameter, willReturn: CourseDetails...) -> MethodStub { + return Given(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func getCourseDetails(courseID: Parameter, willReturn: CourseDetails...) -> MethodStub { + return Given(method: .m_getCourseDetails__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } + public static func enrollToCourse(courseID: Parameter, willReturn: Bool...) -> MethodStub { + return Given(method: .m_enrollToCourse__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func discovery(page: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_discovery__page_page(`page`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -1484,6 +1622,36 @@ open class DiscoveryInteractorProtocolMock: DiscoveryInteractorProtocol, Mock { willProduce(stubber) return given } + public static func getLoadedCourseDetails(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getLoadedCourseDetails(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (CourseDetails).self) + willProduce(stubber) + return given + } + public static func getCourseDetails(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getCourseDetails__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getCourseDetails(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getCourseDetails__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (CourseDetails).self) + willProduce(stubber) + return given + } + public static func enrollToCourse(courseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_enrollToCourse__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func enrollToCourse(courseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_enrollToCourse__courseID_courseID(`courseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Bool).self) + willProduce(stubber) + return given + } } public struct Verify { @@ -1492,6 +1660,9 @@ open class DiscoveryInteractorProtocolMock: DiscoveryInteractorProtocol, Mock { public static func discovery(page: Parameter) -> Verify { return Verify(method: .m_discovery__page_page(`page`))} public static func discoveryOffline() -> Verify { return Verify(method: .m_discoveryOffline)} public static func search(page: Parameter, searchTerm: Parameter) -> Verify { return Verify(method: .m_search__page_pagesearchTerm_searchTerm(`page`, `searchTerm`))} + public static func getLoadedCourseDetails(courseID: Parameter) -> Verify { return Verify(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`))} + public static func getCourseDetails(courseID: Parameter) -> Verify { return Verify(method: .m_getCourseDetails__courseID_courseID(`courseID`))} + public static func enrollToCourse(courseID: Parameter) -> Verify { return Verify(method: .m_enrollToCourse__courseID_courseID(`courseID`))} } public struct Perform { @@ -1507,6 +1678,15 @@ open class DiscoveryInteractorProtocolMock: DiscoveryInteractorProtocol, Mock { public static func search(page: Parameter, searchTerm: Parameter, perform: @escaping (Int, String) -> Void) -> Perform { return Perform(method: .m_search__page_pagesearchTerm_searchTerm(`page`, `searchTerm`), performs: perform) } + public static func getLoadedCourseDetails(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getLoadedCourseDetails__courseID_courseID(`courseID`), performs: perform) + } + public static func getCourseDetails(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getCourseDetails__courseID_courseID(`courseID`), performs: perform) + } + public static func enrollToCourse(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_enrollToCourse__courseID_courseID(`courseID`), performs: perform) + } } public func given(_ method: Given) { From cec1608382e6c9a8b010699d1fe0d2a71cf6089f Mon Sep 17 00:00:00 2001 From: saeedbashir Date: Wed, 10 Jan 2024 11:03:12 +0500 Subject: [PATCH 05/14] refactor: moving course detail tests to discovery framework --- Course/Course.xcodeproj/project.pbxproj | 12 ------------ Discussion/Discussion.xcodeproj/project.pbxproj | 12 ++++++++++++ .../Discovery}/CourseDetailsViewModelTests.swift | 0 3 files changed, 12 insertions(+), 12 deletions(-) rename {Course/CourseTests/Presentation/Details => Discussion/DiscussionTests/Presentation/Discovery}/CourseDetailsViewModelTests.swift (100%) diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index c7bd05938..d408ec8e0 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ 022F8E182A1E2642008EFAB9 /* EncodedVideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022F8E172A1E2642008EFAB9 /* EncodedVideoPlayerViewModel.swift */; }; 0231124D28EDA804002588FB /* CourseUnitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0231124C28EDA804002588FB /* CourseUnitView.swift */; }; 0231124F28EDA811002588FB /* CourseUnitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0231124E28EDA811002588FB /* CourseUnitViewModel.swift */; }; - 023812E7297AC8EB0087098F /* CourseDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023812E6297AC8EB0087098F /* CourseDetailsViewModelTests.swift */; }; 023812E8297AC8EB0087098F /* Course.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0289F8EE28E1C3510064F8F3 /* Course.framework */; platformFilter = ios; }; 023812F3297AC9ED0087098F /* CourseMock.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023812F2297AC9EC0087098F /* CourseMock.generated.swift */; }; 02454CA02A2618E70043052A /* YouTubeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02454C9F2A2618E70043052A /* YouTubeView.swift */; }; @@ -99,7 +98,6 @@ 0231124C28EDA804002588FB /* CourseUnitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseUnitView.swift; sourceTree = ""; }; 0231124E28EDA811002588FB /* CourseUnitViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseUnitViewModel.swift; sourceTree = ""; }; 023812E4297AC8EA0087098F /* CourseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CourseTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 023812E6297AC8EB0087098F /* CourseDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetailsViewModelTests.swift; sourceTree = ""; }; 023812F2297AC9EC0087098F /* CourseMock.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseMock.generated.swift; sourceTree = ""; }; 02454C9F2A2618E70043052A /* YouTubeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeView.swift; sourceTree = ""; }; 02454CA12A26190A0043052A /* EncodedVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodedVideoView.swift; sourceTree = ""; }; @@ -424,7 +422,6 @@ isa = PBXGroup; children = ( 0766DFD5299ADA4700EBEF6A /* Container */, - 0766DFD6299ADA4F00EBEF6A /* Details */, 0766DFD7299ADA6C00EBEF6A /* Unit */, ); path = Presentation; @@ -439,14 +436,6 @@ path = Container; sourceTree = ""; }; - 0766DFD6299ADA4F00EBEF6A /* Details */ = { - isa = PBXGroup; - children = ( - 023812E6297AC8EB0087098F /* CourseDetailsViewModelTests.swift */, - ); - path = Details; - sourceTree = ""; - }; 0766DFD7299ADA6C00EBEF6A /* Unit */ = { isa = PBXGroup; children = ( @@ -696,7 +685,6 @@ files = ( 0295B1D9297E6DF8003B0C65 /* CourseUnitViewModelTests.swift in Sources */, 02F78AEB29E6BCA20038DE30 /* VideoPlayerViewModelTests.swift in Sources */, - 023812E7297AC8EB0087098F /* CourseDetailsViewModelTests.swift in Sources */, 023812F3297AC9ED0087098F /* CourseMock.generated.swift in Sources */, DB205BFB2AE81B1200136EC2 /* CourseDateViewModelTests.swift in Sources */, 022EA8CB297AD63B0014A8F7 /* CourseContainerViewModelTests.swift in Sources */, diff --git a/Discussion/Discussion.xcodeproj/project.pbxproj b/Discussion/Discussion.xcodeproj/project.pbxproj index 9a659e4a7..967f976a7 100644 --- a/Discussion/Discussion.xcodeproj/project.pbxproj +++ b/Discussion/Discussion.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ 0766DFCA299AA3D400EBEF6A /* Data_CreatedComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0766DFC9299AA3D400EBEF6A /* Data_CreatedComment.swift */; }; 7527943BE0D66C33B167A41A /* Pods_App_Discussion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A7A0375EDDEB8948165EAD /* Pods_App_Discussion.framework */; }; 9FC0EF907C0334E383C300C4 /* Pods_App_Discussion_DiscussionTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C40A586C6164140DC2079231 /* Pods_App_Discussion_DiscussionTests.framework */; }; + E0B9F6A92B4E6A6F00168366 /* CourseDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6A82B4E6A6F00168366 /* CourseDetailsViewModelTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -140,6 +141,7 @@ BBF5EC7249109D6718A229CF /* Pods-App-Discussion.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Discussion/Pods-App-Discussion.debugdev.xcconfig"; sourceTree = ""; }; C214357310D8AC9185B38ABA /* Pods-App-Discussion-DiscussionTests.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion-DiscussionTests.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Discussion-DiscussionTests/Pods-App-Discussion-DiscussionTests.releasestage.xcconfig"; sourceTree = ""; }; C40A586C6164140DC2079231 /* Pods_App_Discussion_DiscussionTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Discussion_DiscussionTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E0B9F6A82B4E6A6F00168366 /* CourseDetailsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetailsViewModelTests.swift; sourceTree = ""; }; ED561A5DE961B1E12B42F4E0 /* Pods-App-Discussion.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Discussion/Pods-App-Discussion.releasedev.xcconfig"; sourceTree = ""; }; F57B9404423B3E624DD25D81 /* Pods-App-Discussion.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Discussion/Pods-App-Discussion.releasestage.xcconfig"; sourceTree = ""; }; FE1509B856949D2742A10836 /* Pods-App-Discussion-DiscussionTests.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion-DiscussionTests.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Discussion-DiscussionTests/Pods-App-Discussion-DiscussionTests.releasedev.xcconfig"; sourceTree = ""; }; @@ -368,6 +370,7 @@ 0766DFD8299ADAB500EBEF6A /* Presentation */ = { isa = PBXGroup; children = ( + E0B9F6A72B4E6A6F00168366 /* Discovery */, 0766DFD9299ADADB00EBEF6A /* CreateNewThread */, 0766DFDC299ADB1D00EBEF6A /* Comment */, 0766DFDE299ADB4900EBEF6A /* Posts */, @@ -459,6 +462,14 @@ path = ../Pods; sourceTree = ""; }; + E0B9F6A72B4E6A6F00168366 /* Discovery */ = { + isa = PBXGroup; + children = ( + E0B9F6A82B4E6A6F00168366 /* CourseDetailsViewModelTests.swift */, + ); + path = Discovery; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -707,6 +718,7 @@ 020767682989528B00B976DE /* CreateNewThreadViewModelTests.swift in Sources */, 0240D8DE2987FF91003CFE50 /* DiscussionMock.generated.swift in Sources */, 02BE57BE298910BD00197812 /* BaseResponsesViewModelTests.swift in Sources */, + E0B9F6A92B4E6A6F00168366 /* CourseDetailsViewModelTests.swift in Sources */, 020767662989393200B976DE /* ResponsesViewModelTests.swift in Sources */, 0201771C29883E96003AC5EF /* ThreadViewModelTests.swift in Sources */, 0240D8D22987FE1F003CFE50 /* PostViewModelTests.swift in Sources */, diff --git a/Course/CourseTests/Presentation/Details/CourseDetailsViewModelTests.swift b/Discussion/DiscussionTests/Presentation/Discovery/CourseDetailsViewModelTests.swift similarity index 100% rename from Course/CourseTests/Presentation/Details/CourseDetailsViewModelTests.swift rename to Discussion/DiscussionTests/Presentation/Discovery/CourseDetailsViewModelTests.swift From f19b0e69846131e77715d829fb9670c091d3e5a7 Mon Sep 17 00:00:00 2001 From: saeedbashir Date: Wed, 10 Jan 2024 11:36:52 +0500 Subject: [PATCH 06/14] refactor: mistankly course detail tests was added in discussion, now adding in discovery --- Discovery/Discovery.xcodeproj/project.pbxproj | 4 ++ .../CourseDetailsViewModelTests.swift | 56 +++++++++---------- .../Discussion.xcodeproj/project.pbxproj | 12 ---- 3 files changed, 32 insertions(+), 40 deletions(-) rename {Discussion/DiscussionTests/Presentation/Discovery => Discovery/DiscoveryTests/Presentation}/CourseDetailsViewModelTests.swift (90%) diff --git a/Discovery/Discovery.xcodeproj/project.pbxproj b/Discovery/Discovery.xcodeproj/project.pbxproj index fa6ef6028..141c611ba 100644 --- a/Discovery/Discovery.xcodeproj/project.pbxproj +++ b/Discovery/Discovery.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ E0B9F6A12B4D57F800168366 /* CourseDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F69B2B4D57F800168366 /* CourseDetailsViewModel.swift */; }; E0B9F6A42B4D59E000168366 /* CourseDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6A32B4D59E000168366 /* CourseDetails.swift */; }; E0B9F6A62B4D620100168366 /* Data_CourseDetailsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6A52B4D620100168366 /* Data_CourseDetailsResponse.swift */; }; + E0B9F6AB2B4E718F00168366 /* CourseDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6AA2B4E718F00168366 /* CourseDetailsViewModelTests.swift */; }; E0D586202B300095009B4BA7 /* DiscoverWebviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */; }; E0D586212B300095009B4BA7 /* DiscoveryWebview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */; }; E0D586232B3000AD009B4BA7 /* DiscoveryURIDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586222B3000AD009B4BA7 /* DiscoveryURIDetails.swift */; }; @@ -99,6 +100,7 @@ E0B9F69B2B4D57F800168366 /* CourseDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetailsViewModel.swift; sourceTree = ""; }; E0B9F6A32B4D59E000168366 /* CourseDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetails.swift; sourceTree = ""; }; E0B9F6A52B4D620100168366 /* Data_CourseDetailsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data_CourseDetailsResponse.swift; sourceTree = ""; }; + E0B9F6AA2B4E718F00168366 /* CourseDetailsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetailsViewModelTests.swift; sourceTree = ""; }; E0D586132B29F25A009B4BA7 /* Authorization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Authorization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverWebviewModel.swift; sourceTree = ""; }; E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryWebview.swift; sourceTree = ""; }; @@ -257,6 +259,7 @@ 0766DFD1299AD97300EBEF6A /* Presentation */ = { isa = PBXGroup; children = ( + E0B9F6AA2B4E718F00168366 /* CourseDetailsViewModelTests.swift */, 022D04892976D7E100E0059B /* DiscoveryViewModelTests.swift */, CFC8494F299BE52C0055E497 /* SearchViewModelTests.swift */, ); @@ -521,6 +524,7 @@ files = ( 022D048A2976D7E100E0059B /* DiscoveryViewModelTests.swift in Sources */, 022D04982976DA8A00E0059B /* DiscoveryMock.generated.swift in Sources */, + E0B9F6AB2B4E718F00168366 /* CourseDetailsViewModelTests.swift in Sources */, CFC84950299BE52C0055E497 /* SearchViewModelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Discussion/DiscussionTests/Presentation/Discovery/CourseDetailsViewModelTests.swift b/Discovery/DiscoveryTests/Presentation/CourseDetailsViewModelTests.swift similarity index 90% rename from Discussion/DiscussionTests/Presentation/Discovery/CourseDetailsViewModelTests.swift rename to Discovery/DiscoveryTests/Presentation/CourseDetailsViewModelTests.swift index 288966a25..ba30a66ce 100644 --- a/Discussion/DiscussionTests/Presentation/Discovery/CourseDetailsViewModelTests.swift +++ b/Discovery/DiscoveryTests/Presentation/CourseDetailsViewModelTests.swift @@ -8,16 +8,16 @@ import SwiftyMocky import XCTest @testable import Core -@testable import Course +@testable import Discovery import Alamofire import SwiftUI final class CourseDetailsViewModelTests: XCTestCase { func testGetCourseDetailSuccess() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() @@ -61,9 +61,9 @@ final class CourseDetailsViewModelTests: XCTestCase { } func testGetCourseDetailSuccessOffline() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() @@ -106,9 +106,9 @@ final class CourseDetailsViewModelTests: XCTestCase { } func testGetCourseDetailNoInternetError() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() @@ -138,9 +138,9 @@ final class CourseDetailsViewModelTests: XCTestCase { } func testGetCourseDetailNoCacheError() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() @@ -168,9 +168,9 @@ final class CourseDetailsViewModelTests: XCTestCase { } func testGetCourseDetailUnknownError() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() @@ -198,9 +198,9 @@ final class CourseDetailsViewModelTests: XCTestCase { } func testEnrollToCourseSuccess() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() @@ -229,9 +229,9 @@ final class CourseDetailsViewModelTests: XCTestCase { } func testEnrollToCourseUnknownError() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() @@ -260,9 +260,9 @@ final class CourseDetailsViewModelTests: XCTestCase { } func testEnrollToCourseNoInternetError() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() @@ -292,9 +292,9 @@ final class CourseDetailsViewModelTests: XCTestCase { } func testEnrollToCourseNoCacheError() async throws { - let interactor = CourseInteractorProtocolMock() - let router = CourseRouterMock() - let analytics = CourseAnalyticsMock() + let interactor = DiscoveryInteractorProtocolMock() + let router = DiscoveryRouterMock() + let analytics = DiscoveryAnalyticsMock() let config = ConfigMock() let cssInjector = CSSInjectorMock() let connectivity = ConnectivityProtocolMock() diff --git a/Discussion/Discussion.xcodeproj/project.pbxproj b/Discussion/Discussion.xcodeproj/project.pbxproj index 967f976a7..9a659e4a7 100644 --- a/Discussion/Discussion.xcodeproj/project.pbxproj +++ b/Discussion/Discussion.xcodeproj/project.pbxproj @@ -59,7 +59,6 @@ 0766DFCA299AA3D400EBEF6A /* Data_CreatedComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0766DFC9299AA3D400EBEF6A /* Data_CreatedComment.swift */; }; 7527943BE0D66C33B167A41A /* Pods_App_Discussion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A7A0375EDDEB8948165EAD /* Pods_App_Discussion.framework */; }; 9FC0EF907C0334E383C300C4 /* Pods_App_Discussion_DiscussionTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C40A586C6164140DC2079231 /* Pods_App_Discussion_DiscussionTests.framework */; }; - E0B9F6A92B4E6A6F00168366 /* CourseDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6A82B4E6A6F00168366 /* CourseDetailsViewModelTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -141,7 +140,6 @@ BBF5EC7249109D6718A229CF /* Pods-App-Discussion.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Discussion/Pods-App-Discussion.debugdev.xcconfig"; sourceTree = ""; }; C214357310D8AC9185B38ABA /* Pods-App-Discussion-DiscussionTests.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion-DiscussionTests.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Discussion-DiscussionTests/Pods-App-Discussion-DiscussionTests.releasestage.xcconfig"; sourceTree = ""; }; C40A586C6164140DC2079231 /* Pods_App_Discussion_DiscussionTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Discussion_DiscussionTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E0B9F6A82B4E6A6F00168366 /* CourseDetailsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetailsViewModelTests.swift; sourceTree = ""; }; ED561A5DE961B1E12B42F4E0 /* Pods-App-Discussion.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Discussion/Pods-App-Discussion.releasedev.xcconfig"; sourceTree = ""; }; F57B9404423B3E624DD25D81 /* Pods-App-Discussion.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Discussion/Pods-App-Discussion.releasestage.xcconfig"; sourceTree = ""; }; FE1509B856949D2742A10836 /* Pods-App-Discussion-DiscussionTests.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Discussion-DiscussionTests.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Discussion-DiscussionTests/Pods-App-Discussion-DiscussionTests.releasedev.xcconfig"; sourceTree = ""; }; @@ -370,7 +368,6 @@ 0766DFD8299ADAB500EBEF6A /* Presentation */ = { isa = PBXGroup; children = ( - E0B9F6A72B4E6A6F00168366 /* Discovery */, 0766DFD9299ADADB00EBEF6A /* CreateNewThread */, 0766DFDC299ADB1D00EBEF6A /* Comment */, 0766DFDE299ADB4900EBEF6A /* Posts */, @@ -462,14 +459,6 @@ path = ../Pods; sourceTree = ""; }; - E0B9F6A72B4E6A6F00168366 /* Discovery */ = { - isa = PBXGroup; - children = ( - E0B9F6A82B4E6A6F00168366 /* CourseDetailsViewModelTests.swift */, - ); - path = Discovery; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -718,7 +707,6 @@ 020767682989528B00B976DE /* CreateNewThreadViewModelTests.swift in Sources */, 0240D8DE2987FF91003CFE50 /* DiscussionMock.generated.swift in Sources */, 02BE57BE298910BD00197812 /* BaseResponsesViewModelTests.swift in Sources */, - E0B9F6A92B4E6A6F00168366 /* CourseDetailsViewModelTests.swift in Sources */, 020767662989393200B976DE /* ResponsesViewModelTests.swift in Sources */, 0201771C29883E96003AC5EF /* ThreadViewModelTests.swift in Sources */, 0240D8D22987FE1F003CFE50 /* PostViewModelTests.swift in Sources */, From 4779971b662325034f2f75a19a895316f1f6d3b3 Mon Sep 17 00:00:00 2001 From: saeedbashir Date: Wed, 10 Jan 2024 20:03:54 +0500 Subject: [PATCH 07/14] fix: fix the unresponsive clicks of webview --- Core/Core/View/Base/WebView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Core/View/Base/WebView.swift b/Core/Core/View/Base/WebView.swift index e0ff08e46..670d9516d 100644 --- a/Core/Core/View/Base/WebView.swift +++ b/Core/Core/View/Base/WebView.swift @@ -31,7 +31,7 @@ public struct WebView: UIViewRepresentable { @ObservedObject var viewModel: ViewModel @Binding public var isLoading: Bool - weak var webViewNavDelegate: WebViewNavigationDelegate? + var webViewNavDelegate: WebViewNavigationDelegate? var refreshCookies: () async -> Void From 80c77992e9792a7ff7f6625183c0e110a7024e19 Mon Sep 17 00:00:00 2001 From: Saeed Bashir Date: Mon, 15 Jan 2024 12:57:44 +0500 Subject: [PATCH 08/14] refactor: address review feedback --- Core/Core/View/Base/WebView.swift | 5 ++-- Course/Course/uk.lproj/Localizable.strings | 5 ---- .../Presentation/DiscoveryRouter.swift | 12 +++++++-- .../WebDiscovery/DiscoverWebviewModel.swift | 2 +- .../WebDiscovery/URL+PathExtension.swift | 2 +- .../Discovery/uk.lproj/Localizable.strings | 5 ++++ OpenEdX/Data/DiscoveryPersistence.swift | 26 ++++++++++--------- 7 files changed, 33 insertions(+), 24 deletions(-) diff --git a/Core/Core/View/Base/WebView.swift b/Core/Core/View/Base/WebView.swift index 670d9516d..85859e1f3 100644 --- a/Core/Core/View/Base/WebView.swift +++ b/Core/Core/View/Base/WebView.swift @@ -99,7 +99,8 @@ public struct WebView: UIViewRepresentable { let isWebViewDelegateHandled = await ( parent.webViewNavDelegate?.webView( webView, - shouldLoad: navigationAction.request, navigationAction: navigationAction) ?? false + shouldLoad: navigationAction.request, + navigationAction: navigationAction) ?? false ) if isWebViewDelegateHandled { @@ -188,8 +189,6 @@ public struct WebView: UIViewRepresentable { webView.backgroundColor = .clear webView.scrollView.backgroundColor = Theme.Colors.white.uiColor() webView.scrollView.alwaysBounceVertical = false -// webView.scrollView.layer.cornerRadius = 24 -// webView.scrollView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] webView.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 200, right: 0) return webView diff --git a/Course/Course/uk.lproj/Localizable.strings b/Course/Course/uk.lproj/Localizable.strings index 180474854..59c7bce6a 100644 --- a/Course/Course/uk.lproj/Localizable.strings +++ b/Course/Course/uk.lproj/Localizable.strings @@ -6,11 +6,6 @@ */ -"DETAILS.TITLE" = "Деталі курсу"; -"DETAILS.VIEW_COURSE" = "Переглянути курс"; -"DETAILS.ENROLL_NOW" = "Зареєструватися"; -"DETAILS.ENROLLMENT_DATE_IS_OVER" = "Ви не можете зареєструватися на цей курс, оскільки дата реєстрації закінчилася."; - "OUTLINE.CONGRATULATIONS" = "Вітаємо!"; "OUTLINE.PASSED_THE_COURSE" = "Ви пройшли курс"; "OUTLINE.VIEW_CERTIFICATE" = "Переглянути сертифікат"; diff --git a/Discovery/Discovery/Presentation/DiscoveryRouter.swift b/Discovery/Discovery/Presentation/DiscoveryRouter.swift index c8fd80b29..cf47d2204 100644 --- a/Discovery/Discovery/Presentation/DiscoveryRouter.swift +++ b/Discovery/Discovery/Presentation/DiscoveryRouter.swift @@ -10,7 +10,11 @@ import Core public protocol DiscoveryRouter: BaseRouter { func showCourseDetais(courseID: String, title: String) - func showWebDiscoveryDetails(pathID: String, discoveryType: DiscoveryWebviewType, sourceScreen: LogistrationSourceScreen) + func showWebDiscoveryDetails( + pathID: String, + discoveryType: DiscoveryWebviewType, + sourceScreen: LogistrationSourceScreen + ) func showUpdateRequiredView(showAccountLink: Bool) func showUpdateRecomendedView() func showDiscoverySearch(searchQuery: String?) @@ -32,7 +36,11 @@ public class DiscoveryRouterMock: BaseRouterMock, DiscoveryRouter { public override init() {} public func showCourseDetais(courseID: String, title: String) {} - public func showWebDiscoveryDetails(pathID: String, discoveryType: DiscoveryWebviewType, sourceScreen: LogistrationSourceScreen) {} + public func showWebDiscoveryDetails( + pathID: String, + discoveryType: DiscoveryWebviewType, + sourceScreen: LogistrationSourceScreen + ) {} public func showUpdateRequiredView(showAccountLink: Bool) {} public func showUpdateRecomendedView() {} public func showDiscoverySearch(searchQuery: String? = nil) {} diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift index d68e8a83d..09266c14f 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift @@ -33,7 +33,7 @@ public class DiscoveryWebviewModel: ObservableObject { var sourceScreen: LogistrationSourceScreen var userloggedIn: Bool { - return !(storage.user?.username?.isEmpty ?? true) + return storage.user?.username?.isEmpty == false } public init( diff --git a/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift b/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift index ca89f7d77..01a5faabf 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift @@ -13,7 +13,7 @@ public extension URL { } var isValidAppURLScheme: Bool { - return scheme ?? "" == URIString.appURLScheme.rawValue ? true : false + return scheme ?? "" == URIString.appURLScheme.rawValue && } var queryParameters: [String: Any]? { diff --git a/Discovery/Discovery/uk.lproj/Localizable.strings b/Discovery/Discovery/uk.lproj/Localizable.strings index 63109fae9..28f832e27 100644 --- a/Discovery/Discovery/uk.lproj/Localizable.strings +++ b/Discovery/Discovery/uk.lproj/Localizable.strings @@ -29,3 +29,8 @@ "ALERT.LEAVING_APP_TITLE" = "Leaving the app"; "ALERT.LEAVING_APP_MESSAGE" = "You are now leaving the app and opening a browser"; + +"DETAILS.TITLE" = "Деталі курсу"; +"DETAILS.VIEW_COURSE" = "Переглянути курс"; +"DETAILS.ENROLL_NOW" = "Зареєструватися"; +"DETAILS.ENROLLMENT_DATE_IS_OVER" = "Ви не можете зареєструватися на цей курс, оскільки дата реєстрації закінчилася."; diff --git a/OpenEdX/Data/DiscoveryPersistence.swift b/OpenEdX/Data/DiscoveryPersistence.swift index b56bfa20e..189264f41 100644 --- a/OpenEdX/Data/DiscoveryPersistence.swift +++ b/OpenEdX/Data/DiscoveryPersistence.swift @@ -71,18 +71,20 @@ public class DiscoveryPersistence: DiscoveryPersistenceProtocol { let request = CDCourseDetails.fetchRequest() request.predicate = NSPredicate(format: "courseID = %@", courseID) guard let courseDetails = try? context.fetch(request).first else { throw NoCachedDataError() } - return CourseDetails(courseID: courseDetails.courseID ?? "", - org: courseDetails.org ?? "", - courseTitle: courseDetails.courseTitle ?? "", - courseDescription: courseDetails.courseDescription ?? "", - courseStart: courseDetails.courseStart, - courseEnd: courseDetails.courseEnd, - enrollmentStart: courseDetails.enrollmentStart, - enrollmentEnd: courseDetails.enrollmentEnd, - isEnrolled: courseDetails.isEnrolled, - overviewHTML: courseDetails.overviewHTML ?? "", - courseBannerURL: courseDetails.courseBannerURL ?? "", - courseVideoURL: nil) + return CourseDetails( + courseID: courseDetails.courseID ?? "", + org: courseDetails.org ?? "", + courseTitle: courseDetails.courseTitle ?? "", + courseDescription: courseDetails.courseDescription ?? "", + courseStart: courseDetails.courseStart, + courseEnd: courseDetails.courseEnd, + enrollmentStart: courseDetails.enrollmentStart, + enrollmentEnd: courseDetails.enrollmentEnd, + isEnrolled: courseDetails.isEnrolled, + overviewHTML: courseDetails.overviewHTML ?? "", + courseBannerURL: courseDetails.courseBannerURL ?? "", + courseVideoURL: nil + ) } public func saveCourseDetails(course: CourseDetails) { From 370d2c70175466b3065d21050079d72d1554b829 Mon Sep 17 00:00:00 2001 From: Saeed Bashir Date: Mon, 15 Jan 2024 16:04:14 +0500 Subject: [PATCH 09/14] fix: fix typo --- .../Discovery/Presentation/WebDiscovery/URL+PathExtension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift b/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift index 01a5faabf..bc2042f6a 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift @@ -13,7 +13,7 @@ public extension URL { } var isValidAppURLScheme: Bool { - return scheme ?? "" == URIString.appURLScheme.rawValue && + return scheme ?? "" == URIString.appURLScheme.rawValue } var queryParameters: [String: Any]? { From 8d28140ae7535251daaf8a7f6afdc8682b87d5b6 Mon Sep 17 00:00:00 2001 From: Saeed Bashir Date: Mon, 15 Jan 2024 21:13:36 +0500 Subject: [PATCH 10/14] refactor: rename class --- Discovery/Discovery.xcodeproj/project.pbxproj | 8 ++++---- .../Presentation/WebDiscovery/DiscoveryWebview.swift | 4 ++-- ...WebviewModel.swift => DiscoveryWebviewViewModel.swift} | 8 +++----- OpenEdX/DI/ScreenAssembly.swift | 4 ++-- OpenEdX/Router.swift | 4 ++-- OpenEdX/View/MainScreenView.swift | 2 +- 6 files changed, 14 insertions(+), 16 deletions(-) rename Discovery/Discovery/Presentation/WebDiscovery/{DiscoverWebviewModel.swift => DiscoveryWebviewViewModel.swift} (97%) diff --git a/Discovery/Discovery.xcodeproj/project.pbxproj b/Discovery/Discovery.xcodeproj/project.pbxproj index 141c611ba..8c3b38206 100644 --- a/Discovery/Discovery.xcodeproj/project.pbxproj +++ b/Discovery/Discovery.xcodeproj/project.pbxproj @@ -37,7 +37,7 @@ E0B9F6A42B4D59E000168366 /* CourseDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6A32B4D59E000168366 /* CourseDetails.swift */; }; E0B9F6A62B4D620100168366 /* Data_CourseDetailsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6A52B4D620100168366 /* Data_CourseDetailsResponse.swift */; }; E0B9F6AB2B4E718F00168366 /* CourseDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B9F6AA2B4E718F00168366 /* CourseDetailsViewModelTests.swift */; }; - E0D586202B300095009B4BA7 /* DiscoverWebviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */; }; + E0D586202B300095009B4BA7 /* DiscoveryWebviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861E2B300095009B4BA7 /* DiscoveryWebviewViewModel.swift */; }; E0D586212B300095009B4BA7 /* DiscoveryWebview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */; }; E0D586232B3000AD009B4BA7 /* DiscoveryURIDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586222B3000AD009B4BA7 /* DiscoveryURIDetails.swift */; }; E0D586252B300134009B4BA7 /* URL+PathExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586242B300134009B4BA7 /* URL+PathExtension.swift */; }; @@ -102,7 +102,7 @@ E0B9F6A52B4D620100168366 /* Data_CourseDetailsResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data_CourseDetailsResponse.swift; sourceTree = ""; }; E0B9F6AA2B4E718F00168366 /* CourseDetailsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDetailsViewModelTests.swift; sourceTree = ""; }; E0D586132B29F25A009B4BA7 /* Authorization.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Authorization.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverWebviewModel.swift; sourceTree = ""; }; + E0D5861E2B300095009B4BA7 /* DiscoveryWebviewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryWebviewViewModel.swift; sourceTree = ""; }; E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryWebview.swift; sourceTree = ""; }; E0D586222B3000AD009B4BA7 /* DiscoveryURIDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryURIDetails.swift; sourceTree = ""; }; E0D586242B300134009B4BA7 /* URL+PathExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+PathExtension.swift"; sourceTree = ""; }; @@ -315,7 +315,7 @@ E0D5861D2B300095009B4BA7 /* WebDiscovery */ = { isa = PBXGroup; children = ( - E0D5861E2B300095009B4BA7 /* DiscoverWebviewModel.swift */, + E0D5861E2B300095009B4BA7 /* DiscoveryWebviewViewModel.swift */, E0D5861F2B300095009B4BA7 /* DiscoveryWebview.swift */, E0D586222B3000AD009B4BA7 /* DiscoveryURIDetails.swift */, E0D586242B300134009B4BA7 /* URL+PathExtension.swift */, @@ -553,7 +553,7 @@ 029242EB2AE6AB7B00A940EC /* UpdateNotificationView.swift in Sources */, 02EF39DC28D86BEF0058F6BD /* Strings.swift in Sources */, E0B9F69C2B4D57F800168366 /* SearchView.swift in Sources */, - E0D586202B300095009B4BA7 /* DiscoverWebviewModel.swift in Sources */, + E0D586202B300095009B4BA7 /* DiscoveryWebviewViewModel.swift in Sources */, 02F1752F2A4DA3B60019CD70 /* DiscoveryAnalytics.swift in Sources */, E0D586212B300095009B4BA7 /* DiscoveryWebview.swift in Sources */, ); diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift index c0b79bba8..9aff5c6f1 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift @@ -20,7 +20,7 @@ public struct DiscoveryWebview: View { @State private var searchQuery: String = "" @State private var isLoading: Bool = true - @ObservedObject private var viewModel: DiscoveryWebviewModel + @ObservedObject private var viewModel: DiscoveryWebviewViewModel private var router: DiscoveryRouter private var discoveryType: DiscoveryWebviewType private var pathID: String @@ -64,7 +64,7 @@ public struct DiscoveryWebview: View { } public init( - viewModel: DiscoveryWebviewModel, + viewModel: DiscoveryWebviewViewModel, router: DiscoveryRouter, searchQuery: String? = nil, discoveryType: DiscoveryWebviewType = .discovery, diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift similarity index 97% rename from Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift rename to Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift index 09266c14f..466368b35 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoverWebviewModel.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift @@ -1,5 +1,5 @@ // -// DiscoverWebviewModel.swift +// DiscoveryWebviewViewModel.swift // Discovery // // Created by SaeedBashir on 12/16/23. @@ -7,11 +7,9 @@ import Foundation import Core -import WebKit import SwiftUI -import Swinject -public class DiscoveryWebviewModel: ObservableObject { +public class DiscoveryWebviewViewModel: ObservableObject { @Published var courseDetails: CourseDetails? @Published private(set) var showProgress = false @Published var showError: Bool = false @@ -111,7 +109,7 @@ public class DiscoveryWebviewModel: ObservableObject { } } -extension DiscoveryWebviewModel: WebViewNavigationDelegate { +extension DiscoveryWebviewViewModel: WebViewNavigationDelegate { public func webView( _ webView: WKWebView, shouldLoad request: URLRequest, diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index 9f58df371..5134168b8 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -109,8 +109,8 @@ class ScreenAssembly: Assembly { ) } - container.register(DiscoveryWebviewModel.self) { r, sourceScreen in - DiscoveryWebviewModel( + container.register(DiscoveryWebviewViewModel.self) { r, sourceScreen in + DiscoveryWebviewViewModel( router: r.resolve(DiscoveryRouter.self)!, config: r.resolve(ConfigProtocol.self)!, interactor: r.resolve(DiscoveryInteractorProtocol.self)!, diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index d5d34219e..9819bf335 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -217,7 +217,7 @@ public class Router: AuthorizationRouter, ) { let view = DiscoveryWebview( viewModel: Container.shared.resolve( - DiscoveryWebviewModel.self, + DiscoveryWebviewViewModel.self, argument: sourceScreen)!, router: Container.shared.resolve(DiscoveryRouter.self)!, discoveryType: discoveryType, @@ -252,7 +252,7 @@ public class Router: AuthorizationRouter, } else if config?.discovery.type == .webview { let view = DiscoveryWebview( viewModel: Container.shared.resolve( - DiscoveryWebviewModel.self, + DiscoveryWebviewViewModel.self, argument: sourceScreen )!, router: Container.shared.resolve(DiscoveryRouter.self)!, diff --git a/OpenEdX/View/MainScreenView.swift b/OpenEdX/View/MainScreenView.swift index 9bd8588c3..effc51c38 100644 --- a/OpenEdX/View/MainScreenView.swift +++ b/OpenEdX/View/MainScreenView.swift @@ -53,7 +53,7 @@ struct MainScreenView: View { } else if config?.discovery.type == .webview { DiscoveryWebview( viewModel: Container.shared.resolve( - DiscoveryWebviewModel.self, + DiscoveryWebviewViewModel.self, argument: viewModel.sourceScreen)!, router: Container.shared.resolve(DiscoveryRouter.self)! ) From 4cc33df8415ff0d470fa026d74980a1058d7908d Mon Sep 17 00:00:00 2001 From: Saeed Bashir Date: Mon, 15 Jan 2024 21:34:57 +0500 Subject: [PATCH 11/14] fix: add the missing import --- .../Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift index 466368b35..c1bded8f4 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebviewViewModel.swift @@ -8,6 +8,7 @@ import Foundation import Core import SwiftUI +import WebKit public class DiscoveryWebviewViewModel: ObservableObject { @Published var courseDetails: CourseDetails? From 4c9ab0324057729f1c308613f4ccccb2d8d91399 Mon Sep 17 00:00:00 2001 From: Saeed Bashir Date: Tue, 16 Jan 2024 19:04:38 +0500 Subject: [PATCH 12/14] refactor: removing extra space --- .../Presentation/WebDiscovery/DiscoveryURIDetails.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryURIDetails.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryURIDetails.swift index f4e6a6d94..3eff65da9 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryURIDetails.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryURIDetails.swift @@ -14,7 +14,7 @@ public enum URIString: String { case pathPlaceHolder = "{path_id}" } -public enum URLParameterKeys: String, RawStringExtractable { +public enum URLParameterKeys: String, RawStringExtractable { case pathId = "path_id" case courseId = "course_id" case emailOptIn = "email_opt_in" From acec545e1615a9bdc5c52c12f21adbfa9c5d9733 Mon Sep 17 00:00:00 2001 From: Saeed Bashir Date: Wed, 17 Jan 2024 10:36:41 +0500 Subject: [PATCH 13/14] refactor: move notication name to central place --- Core/Core/Extensions/Notification.swift | 1 + Core/Core/View/Base/Webview/WebView.swift | 4 +--- .../Presentation/WebDiscovery/DiscoveryWebview.swift | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Core/Core/Extensions/Notification.swift b/Core/Core/Extensions/Notification.swift index 1a745398a..2f119ce9e 100644 --- a/Core/Core/Extensions/Notification.swift +++ b/Core/Core/Extensions/Notification.swift @@ -13,4 +13,5 @@ public extension Notification.Name { static let onActualVersionReceived = Notification.Name("onActualVersionReceived") static let onAppUpgradeAccountSettingsTapped = Notification.Name("onAppUpgradeAccountSettingsTapped") static let onNewVersionAvaliable = Notification.Name("onNewVersionAvaliable") + static let webviewReloadNotification = Notification.Name("webviewReloadNotification") } diff --git a/Core/Core/View/Base/Webview/WebView.swift b/Core/Core/View/Base/Webview/WebView.swift index 53885cc5e..ee7e63852 100644 --- a/Core/Core/View/Base/Webview/WebView.swift +++ b/Core/Core/View/Base/Webview/WebView.swift @@ -10,8 +10,6 @@ import WebKit import SwiftUI import Theme -public let WebviewReloadNotification = "webviewReloadNotification" - public protocol WebViewNavigationDelegate: AnyObject { func webView(_ webView: WKWebView, shouldLoad request: URLRequest, navigationAction: WKNavigationAction) -> Bool } @@ -152,7 +150,7 @@ public struct WebView: UIViewRepresentable { NotificationCenter.default.addObserver( self, selector: #selector(reload), - name: Notification.Name(WebviewReloadNotification), + name: .webviewReloadNotification, object: nil ) } diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift index 9aff5c6f1..a9d801f4d 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift @@ -136,7 +136,7 @@ public struct DiscoveryWebview: View { connectivity: viewModel.connectivity, reloadAction: { NotificationCenter.default.post( - name: NSNotification.Name(rawValue: WebviewReloadNotification), + name: .webviewReloadNotification, object: nil ) }) From 574b74f5f4c963f69bd87922164fd9d871383e17 Mon Sep 17 00:00:00 2001 From: Saeed Bashir Date: Wed, 17 Jan 2024 18:05:01 +0500 Subject: [PATCH 14/14] refactor: address review feedback --- Discovery/Discovery/Data/DiscoveryRepository.swift | 6 +----- Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift | 4 ++-- .../Presentation/WebDiscovery/URL+PathExtension.swift | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Discovery/Discovery/Data/DiscoveryRepository.swift b/Discovery/Discovery/Data/DiscoveryRepository.swift index f644af888..3b20f84a3 100644 --- a/Discovery/Discovery/Data/DiscoveryRepository.swift +++ b/Discovery/Discovery/Data/DiscoveryRepository.swift @@ -73,11 +73,7 @@ public class DiscoveryRepository: DiscoveryRepositoryProtocol { public func enrollToCourse(courseID: String) async throws -> Bool { let enroll = try await api.request(DiscoveryEndpoint.enrollToCourse(courseID: courseID)) - if enroll.statusCode == 200 { - return true - } else { - return false - } + return enroll.statusCode == 200 } } diff --git a/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift b/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift index 848e31d85..957f03112 100644 --- a/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift +++ b/Discovery/Discovery/Data/Network/DiscoveryEndpoint.swift @@ -23,7 +23,7 @@ enum DiscoveryEndpoint: EndPointType { return "/api/courses/v1/courses/" case .getCourseDetail(courseID: let courseID, _): return "/api/courses/v1/courses/\(courseID)" - case .enrollToCourse(courseID: _): + case .enrollToCourse: return "/api/enrollment/v1/enrollment" } } @@ -32,7 +32,7 @@ enum DiscoveryEndpoint: EndPointType { switch self { case .getDiscovery, .searchCourses: return .get - case .getCourseDetail(courseID: _, username: _): + case .getCourseDetail: return .get case .enrollToCourse: return .post diff --git a/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift b/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift index bc2042f6a..fb629e107 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/URL+PathExtension.swift @@ -13,7 +13,7 @@ public extension URL { } var isValidAppURLScheme: Bool { - return scheme ?? "" == URIString.appURLScheme.rawValue + return scheme ?? "" == URIString.appURLScheme.rawValue } var queryParameters: [String: Any]? {