diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index d1f8d0a71..e6c3b9580 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -878,10 +878,10 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } @@ -902,7 +902,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -992,9 +992,10 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -1019,7 +1020,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { @@ -1040,7 +1041,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -1075,7 +1076,7 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -1130,8 +1131,8 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } } @@ -1342,10 +1343,10 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } @@ -1365,7 +1366,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -1450,9 +1451,10 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -1476,7 +1478,7 @@ open class BaseRouterMock: BaseRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { @@ -1496,7 +1498,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -1530,7 +1532,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -1582,8 +1584,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } } diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 192b58f17..0f3115b5f 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -144,6 +144,8 @@ BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */; }; BA8FA66E2AD59E7D00EA029A /* FacebookAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */; }; BA8FA6702AD59EA300EA029A /* MicrosoftAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */; }; + BA981BCE2B8F5C49005707C2 /* Sequence+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA981BCD2B8F5C49005707C2 /* Sequence+Extensions.swift */; }; + BA981BD02B91ED50005707C2 /* FullScreenProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA981BCF2B91ED50005707C2 /* FullScreenProgressView.swift */; }; BAAD62C62AFCF00B000E6103 /* CustomDisclosureGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */; }; BAD9CA2F2B289B3500DE790A /* ajaxHandler.js in Resources */ = {isa = PBXBuildFile; fileRef = BAD9CA2E2B289B3500DE790A /* ajaxHandler.js */; }; BAD9CA332B28A8F300DE790A /* AjaxProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA322B28A8F300DE790A /* AjaxProvider.swift */; }; @@ -321,6 +323,8 @@ BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthProvider.swift; sourceTree = ""; }; BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookAuthProvider.swift; sourceTree = ""; }; BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftAuthProvider.swift; sourceTree = ""; }; + BA981BCD2B8F5C49005707C2 /* Sequence+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Extensions.swift"; sourceTree = ""; }; + BA981BCF2B91ED50005707C2 /* FullScreenProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenProgressView.swift; sourceTree = ""; }; BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisclosureGroup.swift; sourceTree = ""; }; BAD9CA2E2B289B3500DE790A /* ajaxHandler.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = ajaxHandler.js; sourceTree = ""; }; BAD9CA322B28A8F300DE790A /* AjaxProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AjaxProvider.swift; sourceTree = ""; }; @@ -456,6 +460,7 @@ BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */, 02D400602B0678190029D168 /* SKStoreReviewControllerExtension.swift */, E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */, + BA981BCD2B8F5C49005707C2 /* Sequence+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -701,6 +706,7 @@ BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */, BA8FA6672AD59A5700EA029A /* SocialAuthButton.swift */, 02E93F862AEBAED4006C4750 /* AppReview */, + BA981BCF2B91ED50005707C2 /* FullScreenProgressView.swift */, ); path = Base; sourceTree = ""; @@ -1028,6 +1034,7 @@ BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */, 027BD3B32909475900392132 /* Publishers+KeyboardState.swift in Sources */, 0727877D28D25212002E9142 /* ProgressBar.swift in Sources */, + BA981BD02B91ED50005707C2 /* FullScreenProgressView.swift in Sources */, 0236961F28F9A2F600EEF206 /* AuthEndpoint.swift in Sources */, 02B3E3B32930198600A50475 /* AVPlayerViewControllerExtension.swift in Sources */, BAD9CA332B28A8F300DE790A /* AjaxProvider.swift in Sources */, @@ -1063,6 +1070,7 @@ 076F297F2A1F80C800967E7D /* Pagination.swift in Sources */, 02AFCC1A2AEFDC18000360F0 /* ThirdPartyMailer.swift in Sources */, 0770DE5F28D0B22C006D8A5D /* Strings.swift in Sources */, + BA981BCE2B8F5C49005707C2 /* Sequence+Extensions.swift in Sources */, 02C917F029CDA99E00DBB8BD /* Data_Dashboard.swift in Sources */, 024FCD0028EF1CD300232339 /* WebBrowser.swift in Sources */, 027BD3B52909475900392132 /* KeyboardStateObserver.swift in Sources */, diff --git a/Core/Core/Configuration/BaseRouter.swift b/Core/Core/Configuration/BaseRouter.swift index f18897272..2c133f529 100644 --- a/Core/Core/Configuration/BaseRouter.swift +++ b/Core/Core/Configuration/BaseRouter.swift @@ -57,7 +57,7 @@ public protocol BaseRouter { func presentView(transitionStyle: UIModalTransitionStyle, view: any View) - func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) + func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) } @@ -123,7 +123,7 @@ open class BaseRouterMock: BaseRouter { public func presentView(transitionStyle: UIModalTransitionStyle, view: any View) {} - public func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) {} + public func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) {} } #endif diff --git a/Core/Core/Extensions/Notification.swift b/Core/Core/Extensions/Notification.swift index c4d3d70bb..ba9dfe70c 100644 --- a/Core/Core/Extensions/Notification.swift +++ b/Core/Core/Extensions/Notification.swift @@ -16,4 +16,5 @@ public extension Notification.Name { static let webviewReloadNotification = Notification.Name("webviewReloadNotification") static let onBlockCompletion = Notification.Name.init("onBlockCompletion") static let shiftCourseDates = Notification.Name("shiftCourseDates") + static let profileUpdated = Notification.Name("profileUpdated") } diff --git a/Core/Core/Extensions/RawStringExtactable.swift b/Core/Core/Extensions/RawStringExtactable.swift index 8ab42f2ed..1dcedb86c 100644 --- a/Core/Core/Extensions/RawStringExtactable.swift +++ b/Core/Core/Extensions/RawStringExtactable.swift @@ -21,7 +21,7 @@ extension Dictionary: DictionaryExtractionExtension {} public extension DictionaryExtractionExtension where Self.Key == String { - subscript(key :RawStringExtractable) -> Value? { + subscript(key: RawStringExtractable) -> Value? { return self[key.rawValue] } } diff --git a/Core/Core/Extensions/Sequence+Extensions.swift b/Core/Core/Extensions/Sequence+Extensions.swift new file mode 100644 index 000000000..6feea9dd7 --- /dev/null +++ b/Core/Core/Extensions/Sequence+Extensions.swift @@ -0,0 +1,14 @@ +// +// Sequence+Extensions.swift +// Core +// +// Created by Eugene Yatsenko on 28.02.2024. +// + +import Foundation + +public extension Sequence { + func firstAs(_ type: T.Type = T.self) -> T? { + first { $0 is T } as? T + } +} diff --git a/Core/Core/Extensions/ViewExtension.swift b/Core/Core/Extensions/ViewExtension.swift index ef2e493a2..1700e774f 100644 --- a/Core/Core/Extensions/ViewExtension.swift +++ b/Core/Core/Extensions/ViewExtension.swift @@ -258,10 +258,24 @@ public extension View { public extension View { @ViewBuilder - func sheetNavigation(isSheet: Bool) -> some View { + func sheetNavigation(isSheet: Bool, onDismiss: (() -> Void)? = nil) -> some View { if isSheet { NavigationView { self + .if(onDismiss != nil) { view in + view + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + onDismiss?() + } label: { + Image(systemName: "xmark") + .foregroundColor(Theme.Colors.accentColor) + } + .accessibilityIdentifier("close_button") + } + } + } } } else { self diff --git a/Core/Core/SwiftGen/Strings.swift b/Core/Core/SwiftGen/Strings.swift index 3e8883c3b..de944968e 100644 --- a/Core/Core/SwiftGen/Strings.swift +++ b/Core/Core/SwiftGen/Strings.swift @@ -16,6 +16,8 @@ public enum CoreLocalization { public static let socialSignCanceled = CoreLocalization.tr("Localizable", "SOCIAL_SIGN_CANCELED", fallback: "The user canceled the sign-in flow.") /// Tomorrow public static let tomorrow = CoreLocalization.tr("Localizable", "TOMORROW", fallback: "Tomorrow") + /// View + public static let view = CoreLocalization.tr("Localizable", "VIEW ", fallback: "View") /// Yesterday public static let yesterday = CoreLocalization.tr("Localizable", "YESTERDAY", fallback: "Yesterday") public enum Alert { diff --git a/Core/Core/View/Base/AlertView.swift b/Core/Core/View/Base/AlertView.swift index ff79856c4..75ff3f8e3 100644 --- a/Core/Core/View/Base/AlertView.swift +++ b/Core/Core/View/Base/AlertView.swift @@ -14,12 +14,13 @@ public enum AlertViewType: Equatable { case logOut case leaveProfile case deleteVideo + case deepLink var contentPadding: CGFloat { switch self { case .`default`: return 16 - case .action, .logOut, .leaveProfile, .deleteVideo: + case .action, .logOut, .leaveProfile, .deleteVideo, .deepLink: return 36 } } @@ -146,15 +147,14 @@ public struct AlertView: View { .multilineTextAlignment(.center) .padding(.horizontal, 40) .frame(maxWidth: 250) - case .leaveProfile, .deleteVideo: + case .leaveProfile, .deleteVideo, .deepLink: VStack(spacing: 20) { - if type == .deleteVideo { - CoreAssets.warning.swiftUIImage.renderingMode(.template) - .foregroundColor(Theme.Colors.textPrimary) + switch type { + case .deleteVideo, .deepLink: + CoreAssets.warning.swiftUIImage .padding(.top, isHorizontal ? 20 : 54) - } else { - CoreAssets.leaveProfile.swiftUIImage.renderingMode(.template) - .foregroundColor(Theme.Colors.textPrimary) + default: + CoreAssets.leaveProfile.swiftUIImage .padding(.top, isHorizontal ? 20 : 54) } Text(alertTitle) @@ -353,69 +353,84 @@ public struct AlertView: View { } .padding(.trailing, isHorizontal ? 20 : 0) case .deleteVideo: - VStack(spacing: 0) { - Button { - okTapped() - } label: { - ZStack { - Text(CoreLocalization.Alert.delete) - .foregroundColor(Theme.Colors.primaryButtonTextColor) - .font(Theme.Fonts.labelLarge) - .frame(maxWidth: .infinity) - .padding(.horizontal, 16) - } - .frame(maxWidth: 215, minHeight: 48) - } - .background( - Theme.Shapes.buttonShape - .fill(Theme.Colors.accentColor) - ) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(style: .init( - lineWidth: 1, - lineCap: .round, - lineJoin: .round, - miterLimit: 1 - )) - .foregroundColor(.clear) - ) - .frame(maxWidth: 215) - .padding(.bottom, isHorizontal ? 10 : 24) - Button(action: { - onCloseTapped() - }, label: { - ZStack { - Text(CoreLocalization.Alert.cancel) - .foregroundColor(Theme.Colors.secondaryButtonTextColor) - .font(Theme.Fonts.labelLarge) - .frame(maxWidth: .infinity) - .padding(.horizontal, 16) - } - .frame(maxWidth: 215, minHeight: 48) - }) - .background( - Theme.Shapes.buttonShape - .fill(.clear) - ) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(style: .init( - lineWidth: 1, - lineCap: .round, - lineJoin: .round, - miterLimit: 1 - )) - .foregroundColor(Theme.Colors.secondaryButtonBorderColor) - ) - .frame(maxWidth: 215) - } - .padding(.trailing, isHorizontal ? 20 : 0) + configure( + primaryButtonTitle: CoreLocalization.Alert.delete, + secondaryButtonTitle: CoreLocalization.Alert.cancel + ) + case .deepLink: + configure( + primaryButtonTitle: CoreLocalization.view, + secondaryButtonTitle: CoreLocalization.Alert.cancel + ) } } .padding(.top, 16) .padding(.bottom, isHorizontal ? 16 : type.contentPadding) } + + private func configure( + primaryButtonTitle: String, + secondaryButtonTitle: String + ) -> some View { + VStack(spacing: 0) { + Button { + okTapped() + } label: { + ZStack { + Text(primaryButtonTitle) + .foregroundColor(Theme.Colors.primaryButtonTextColor) + .font(Theme.Fonts.labelLarge) + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + } + .frame(maxWidth: 215, minHeight: 48) + } + .background( + Theme.Shapes.buttonShape + .fill(Theme.Colors.accentColor) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + )) + .foregroundColor(.clear) + ) + .frame(maxWidth: 215) + .padding(.bottom, isHorizontal ? 10 : 24) + Button(action: { + onCloseTapped() + }, label: { + ZStack { + Text(secondaryButtonTitle) + .foregroundColor(Theme.Colors.secondaryButtonTextColor) + .font(Theme.Fonts.labelLarge) + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + } + .frame(maxWidth: 215, minHeight: 48) + }) + .background( + Theme.Shapes.buttonShape + .fill(.clear) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + )) + .foregroundColor(Theme.Colors.secondaryButtonBorderColor) + ) + .frame(maxWidth: 215) + } + .padding(.trailing, isHorizontal ? 20 : 0) + } } // swiftlint:disable all diff --git a/Core/Core/View/Base/FullScreenProgressView.swift b/Core/Core/View/Base/FullScreenProgressView.swift new file mode 100644 index 000000000..8a49dc0f6 --- /dev/null +++ b/Core/Core/View/Base/FullScreenProgressView.swift @@ -0,0 +1,51 @@ +// +// FullScreenProgressView.swift +// Core +// +// Created by Eugene Yatsenko on 01.03.2024. +// + +import SwiftUI +import Theme + +public struct FullScreenProgressView: View { + + @Environment(\.dismiss) private var dismiss + + public init() {} + + public var body: some View { + ZStack(alignment: .center) { + Color.black.opacity(0.8) + .onTapGesture { + dismiss() + } + VStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + .padding(.horizontal) + .padding(.vertical, 50) + } + .frame(maxWidth: 140) + .background( + Theme.Shapes.cardShape + .fill(Theme.Colors.cardViewBackground) + .shadow(radius: 24) + .fixedSize(horizontal: false, vertical: false) + ) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke( + style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + ) + ) + .foregroundColor(Theme.Colors.backgroundStroke) + .fixedSize(horizontal: false, vertical: false) + ) + } + .ignoresSafeArea() + } +} diff --git a/Core/Core/View/Base/PickerView.swift b/Core/Core/View/Base/PickerView.swift index 91be4684b..868a5e600 100644 --- a/Core/Core/View/Base/PickerView.swift +++ b/Core/Core/View/Base/PickerView.swift @@ -33,7 +33,10 @@ public struct PickerView: View { Animation.easeInOut(duration: 0.3) ) { let pickerItems = config.field.options.map { PickerItem(key: $0.value, value: $0.name) } - router.presentView(transitionStyle: .crossDissolve) { + router.presentView( + transitionStyle: .crossDissolve, + animated: true + ) { PickerMenu(items: pickerItems, titleText: config.field.label, router: router, diff --git a/Core/Core/en.lproj/Localizable.strings b/Core/Core/en.lproj/Localizable.strings index 7bfaab4e1..230ab3676 100644 --- a/Core/Core/en.lproj/Localizable.strings +++ b/Core/Core/en.lproj/Localizable.strings @@ -74,6 +74,7 @@ "SETTINGS.DOWNLOAD_QUALITY_720_DESCRIPTION" = "Best quality"; "DONE" = "Done"; +"VIEW " = "View"; "PICKER.SEARCH" = "Search"; "PICKER.ACCEPT" = "Accept"; diff --git a/Course/Course/Domain/Model/CourseUpdate.swift b/Course/Course/Domain/Model/CourseUpdate.swift index 2ef09f210..b7d0dab06 100644 --- a/Course/Course/Domain/Model/CourseUpdate.swift +++ b/Course/Course/Domain/Model/CourseUpdate.swift @@ -7,7 +7,7 @@ import Foundation -public struct CourseUpdate { +public struct CourseUpdate: Hashable { public let id: Int public let date: String public var content: String diff --git a/Course/Course/Presentation/Container/CourseContainerView.swift b/Course/Course/Presentation/Container/CourseContainerView.swift index ea30c67e3..54c0cca35 100644 --- a/Course/Course/Presentation/Container/CourseContainerView.swift +++ b/Course/Course/Presentation/Container/CourseContainerView.swift @@ -13,54 +13,10 @@ import Theme public struct CourseContainerView: View { - enum CourseTab: Int, CaseIterable, Identifiable { - var id: Int { - rawValue - } - - case course - case videos - case discussion - case dates - case handounds - - var title: String { - switch self { - case .course: - return CourseLocalization.CourseContainer.course - case .videos: - return CourseLocalization.CourseContainer.videos - case .dates: - return CourseLocalization.CourseContainer.dates - case .discussion: - return CourseLocalization.CourseContainer.discussions - case .handounds: - return CourseLocalization.CourseContainer.handouts - } - } - - var image: Image { - switch self { - case .course: - return CoreAssets.bookCircle.swiftUIImage.renderingMode(.template) - case .videos: - return CoreAssets.videoCircle.swiftUIImage.renderingMode(.template) - case .dates: - return Image(systemName: "calendar").renderingMode(.template) - case .discussion: - return CoreAssets.bubbleLeftCircle.swiftUIImage.renderingMode(.template) - case .handounds: - return CoreAssets.docCircle.swiftUIImage.renderingMode(.template) - } - } - - } - @ObservedObject - private var viewModel: CourseContainerViewModel - @State private var selection: Int = CourseTab.course.rawValue + public var viewModel: CourseContainerViewModel @State private var isAnimatingForTap: Bool = false - private var courseID: String + public var courseID: String private var title: String public init( @@ -90,7 +46,7 @@ public struct CourseContainerView: View { .navigationBarHidden(false) .navigationBarBackButtonHidden(false) .navigationTitle(title) - .onChange(of: selection, perform: didSelect) + .onChange(of: viewModel.selection, perform: didSelect) .background(Theme.Colors.background) } @@ -103,7 +59,7 @@ public struct CourseContainerView: View { title: title, courseID: courseID, isVideo: false, - selection: $selection, + selection: $viewModel.selection, dateTabIndex: CourseTab.dates.rawValue ) } else { @@ -119,11 +75,11 @@ public struct CourseContainerView: View { private var topTabBar: some View { ScrollSlidingTabBar( - selection: $selection, + selection: $viewModel.selection, tabs: CourseTab.allCases.map { $0.title } ) { newValue in isAnimatingForTap = true - selection = newValue + viewModel.selection = newValue DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: .milliseconds(300))) { isAnimatingForTap = false } @@ -131,7 +87,7 @@ public struct CourseContainerView: View { } private var tabs: some View { - TabView(selection: $selection) { + TabView(selection: $viewModel.selection) { ForEach(CourseTab.allCases) { tab in switch tab { case .course: @@ -140,7 +96,7 @@ public struct CourseContainerView: View { title: title, courseID: courseID, isVideo: false, - selection: $selection, + selection: $viewModel.selection, dateTabIndex: CourseTab.dates.rawValue ) .tabItem { @@ -155,7 +111,7 @@ public struct CourseContainerView: View { title: title, courseID: courseID, isVideo: true, - selection: $selection, + selection: $viewModel.selection, dateTabIndex: CourseTab.dates.rawValue ) .tabItem { @@ -206,7 +162,7 @@ public struct CourseContainerView: View { .if(viewModel.config.uiComponents.courseTopTabBarEnabled) { view in view .tabViewStyle(.page(indexDisplayMode: .never)) - .animation(.default, value: selection) + .animation(.default, value: viewModel.selection) } .onFirstAppear { Task { diff --git a/Course/Course/Presentation/Container/CourseContainerViewModel.swift b/Course/Course/Presentation/Container/CourseContainerViewModel.swift index 3398ac421..85418cb27 100644 --- a/Course/Course/Presentation/Container/CourseContainerViewModel.swift +++ b/Course/Course/Presentation/Container/CourseContainerViewModel.swift @@ -10,8 +10,51 @@ import SwiftUI import Core import Combine +public enum CourseTab: Int, CaseIterable, Identifiable { + public var id: Int { + rawValue + } + + case course + case videos + case discussion + case dates + case handounds + + public var title: String { + switch self { + case .course: + return CourseLocalization.CourseContainer.course + case .videos: + return CourseLocalization.CourseContainer.videos + case .dates: + return CourseLocalization.CourseContainer.dates + case .discussion: + return CourseLocalization.CourseContainer.discussions + case .handounds: + return CourseLocalization.CourseContainer.handouts + } + } + + public var image: Image { + switch self { + case .course: + return CoreAssets.bookCircle.swiftUIImage.renderingMode(.template) + case .videos: + return CoreAssets.videoCircle.swiftUIImage.renderingMode(.template) + case .dates: + return Image(systemName: "calendar").renderingMode(.template) + case .discussion: + return CoreAssets.bubbleLeftCircle.swiftUIImage.renderingMode(.template) + case .handounds: + return CoreAssets.docCircle.swiftUIImage.renderingMode(.template) + } + } +} + public class CourseContainerViewModel: BaseCourseViewModel { - + + @Published public var selection: Int = CourseTab.course.rawValue @Published private(set) var isShowProgress = false @Published var courseStructure: CourseStructure? @Published var courseDeadlineInfo: CourseDateBanner? @@ -35,7 +78,7 @@ public class CourseContainerViewModel: BaseCourseViewModel { let router: CourseRouter let config: ConfigProtocol let connectivity: ConnectivityProtocol - + let isActive: Bool? let courseStart: Date? let courseEnd: Date? @@ -219,7 +262,7 @@ public class CourseContainerViewModel: BaseCourseViewModel { } func trackSelectedTab( - selection: CourseContainerView.CourseTab, + selection: CourseTab, courseId: String, courseName: String ) { diff --git a/Course/Course/Presentation/Downloads/DownloadsView.swift b/Course/Course/Presentation/Downloads/DownloadsView.swift index 958380a6d..5c50634ea 100644 --- a/Course/Course/Presentation/Downloads/DownloadsView.swift +++ b/Course/Course/Presentation/Downloads/DownloadsView.swift @@ -39,7 +39,9 @@ public struct DownloadsView: View { public var body: some View { content - .sheetNavigation(isSheet: isSheet) + .sheetNavigation(isSheet: isSheet) { + dismiss() + } } private var content: some View { @@ -53,20 +55,6 @@ public struct DownloadsView: View { } .navigationBarTitleDisplayMode(.inline) .navigationTitle(CourseLocalization.Download.downloads) - .if(isSheet) { view in - view - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { - dismiss() - } label: { - Image(systemName: "xmark") - .foregroundColor(Theme.Colors.accentColor) - } - .accessibilityIdentifier("close_button") - } - } - } .padding(.top, 1) } diff --git a/Course/Course/Presentation/Handouts/HandoutsUpdatesDetailView.swift b/Course/Course/Presentation/Handouts/HandoutsUpdatesDetailView.swift index ff0c51ac5..ac4fbeddd 100644 --- a/Course/Course/Presentation/Handouts/HandoutsUpdatesDetailView.swift +++ b/Course/Course/Presentation/Handouts/HandoutsUpdatesDetailView.swift @@ -17,8 +17,8 @@ public struct HandoutsUpdatesDetailView: View { private var router: CourseRouter private let cssInjector: CSSInjector - private var handouts: String? - private var announcements: [CourseUpdate]? + public var handouts: String? + public var announcements: [CourseUpdate]? private let title: String public init( diff --git a/Course/Course/Presentation/Video/VideoPlayerViewModel.swift b/Course/Course/Presentation/Video/VideoPlayerViewModel.swift index 445a7372c..27b214068 100644 --- a/Course/Course/Presentation/Video/VideoPlayerViewModel.swift +++ b/Course/Course/Presentation/Video/VideoPlayerViewModel.swift @@ -133,7 +133,10 @@ public class VideoPlayerViewModel: ObservableObject { } func presentPicker() { - router.presentView(transitionStyle: .crossDissolve) { + router.presentView( + transitionStyle: .crossDissolve, + animated: true + ) { PickerMenu(items: items, titleText: generateLanguageName(code: selectedLanguage ?? ""), router: router, diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index 9d0340bf9..70218103b 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -599,10 +599,10 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } @@ -622,7 +622,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -707,9 +707,10 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -733,7 +734,7 @@ open class BaseRouterMock: BaseRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { @@ -753,7 +754,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -787,7 +788,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -839,8 +840,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } } diff --git a/Dashboard/DashboardTests/DashboardMock.generated.swift b/Dashboard/DashboardTests/DashboardMock.generated.swift index b8a417000..9e3f24919 100644 --- a/Dashboard/DashboardTests/DashboardMock.generated.swift +++ b/Dashboard/DashboardTests/DashboardMock.generated.swift @@ -599,10 +599,10 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } @@ -622,7 +622,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -707,9 +707,10 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -733,7 +734,7 @@ open class BaseRouterMock: BaseRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { @@ -753,7 +754,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -787,7 +788,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -839,8 +840,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } } diff --git a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift index fc16cdd3e..84052bc6d 100644 --- a/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift +++ b/Discovery/Discovery/Presentation/WebDiscovery/DiscoveryWebview.swift @@ -23,7 +23,7 @@ public struct DiscoveryWebview: View { @ObservedObject private var viewModel: DiscoveryWebviewViewModel private var router: DiscoveryRouter private var discoveryType: DiscoveryWebviewType - private var pathID: String + public var pathID: String private var URLString: String { switch discoveryType { diff --git a/Discovery/Discovery/Presentation/WebPrograms/ProgramWebviewView.swift b/Discovery/Discovery/Presentation/WebPrograms/ProgramWebviewView.swift index 099846af3..f97dd2c8c 100644 --- a/Discovery/Discovery/Presentation/WebPrograms/ProgramWebviewView.swift +++ b/Discovery/Discovery/Presentation/WebPrograms/ProgramWebviewView.swift @@ -21,8 +21,8 @@ public struct ProgramWebviewView: View { @ObservedObject private var viewModel: ProgramWebviewViewModel private var router: DiscoveryRouter private var viewType: ProgramViewType - private var pathID: String - + public var pathID: String + private var URLString: String { switch viewType { case .program: diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index ef22e064f..7c7b67d29 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -599,10 +599,10 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } @@ -622,7 +622,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -707,9 +707,10 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -733,7 +734,7 @@ open class BaseRouterMock: BaseRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { @@ -753,7 +754,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -787,7 +788,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -839,8 +840,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } } diff --git a/Discussion/Discussion/Data/Model/Data_CommentsResponse.swift b/Discussion/Discussion/Data/Model/Data_CommentsResponse.swift index 37649c682..0665d1cac 100644 --- a/Discussion/Discussion/Data/Model/Data_CommentsResponse.swift +++ b/Discussion/Discussion/Data/Model/Data_CommentsResponse.swift @@ -121,6 +121,28 @@ public extension DataLayer { } } +public extension DataLayer.Comments { + var domain: UserComment { + UserComment( + authorName: author ?? DiscussionLocalization.anonymous, + authorAvatar: users?.userName?.profile?.image?.imageURLLarge ?? "", + postDate: Date(iso8601: createdAt), + postTitle: "", + postBody: rawBody, + postBodyHtml: renderedBody, + postVisible: true, + voted: voted, + followed: false, + votesCount: voteCount, + responsesCount: childCount, + threadID: threadID, + commentID: id, + parentID: id, + abuseFlagged: abuseFlagged + ) + } +} + public extension DataLayer.CommentsResponse { var domain: [UserComment] { self.comments.map { comment in diff --git a/Discussion/Discussion/Data/Model/Data_TopicsResponse.swift b/Discussion/Discussion/Data/Model/Data_TopicsResponse.swift index 512d20d29..00724c199 100644 --- a/Discussion/Discussion/Data/Model/Data_TopicsResponse.swift +++ b/Discussion/Discussion/Data/Model/Data_TopicsResponse.swift @@ -36,6 +36,23 @@ extension DataLayer { } } +extension DataLayer.CoursewareTopic { + var domain: CoursewareTopics { + CoursewareTopics( + id: id ?? "", + name: name ?? "", + threadListURL: name ?? "", + children: children.map { + CoursewareTopics( + id: $0.id ?? "", + name: $0.name ?? "", + threadListURL: $0.name ?? "", + children: [] + ) + }) + } +} + extension DataLayer.TopicsResponse { var domain: Topics { let coursewareTopics = coursewareTopics.map { diff --git a/Discussion/Discussion/Data/Network/DiscussionEndpoint.swift b/Discussion/Discussion/Data/Network/DiscussionEndpoint.swift index 1cf9ade1e..4409f22dc 100644 --- a/Discussion/Discussion/Data/Network/DiscussionEndpoint.swift +++ b/Discussion/Discussion/Data/Network/DiscussionEndpoint.swift @@ -12,10 +12,13 @@ import Alamofire enum DiscussionEndpoint: EndPointType { case getCourseDiscussionInfo(courseID: String) case getThreads(courseID: String, type: ThreadType, sort: SortType, filter: ThreadsFilter, page: Int) + case getThread(threadID: String) case getTopics(courseID: String) + case getTopic(courseID: String, topicID: String) case getDiscussionComments(threadID: String, page: Int) case getQuestionComments(threadID: String, page: Int) case getCommentResponses(commentID: String, page: Int) + case getResponse(responseID: String) case addCommentTo(threadID: String, rawBody: String, parentID: String? = nil) case voteThread(voted: Bool, threadID: String) case voteResponse(voted: Bool, responseID: String) @@ -32,14 +35,20 @@ enum DiscussionEndpoint: EndPointType { return "/api/discussion/v1/courses/\(courseID)" case .getThreads: return "/api/discussion/v1/threads/" + case let .getThread(threadID): + return "/api/discussion/v1/threads/\(threadID)/" case let .getTopics(courseID): return "/api/discussion/v1/course_topics/\(courseID)" + case let .getTopic(courseID, _): + return "/api/discussion/v1/course_topics/\(courseID)" case .getDiscussionComments: return "/api/discussion/v1/comments/" case .getQuestionComments: return "/api/discussion/v1/comments/" case let .getCommentResponses(commentID, _): return "/api/discussion/v1/comments/\(commentID)" + case let .getResponse(responseID): + return "/api/discussion/v1/comments/\(responseID)/" case .addCommentTo: return "/api/discussion/v1/comments/" case let .voteThread(_, threadID): @@ -66,9 +75,9 @@ enum DiscussionEndpoint: EndPointType { switch self { case .getCourseDiscussionInfo: return .get - case .getThreads: + case .getThreads, .getThread: return .get - case .getTopics: + case .getTopics, .getTopic: return .get case .getDiscussionComments: return .get @@ -76,6 +85,8 @@ enum DiscussionEndpoint: EndPointType { return .get case .getCommentResponses: return .get + case .getResponse: + return .patch case .addCommentTo: return .post case .voteThread: @@ -101,7 +112,9 @@ enum DiscussionEndpoint: EndPointType { switch self { case .getCourseDiscussionInfo, .getThreads, + .getThread, .getTopics, + .getTopic, .getDiscussionComments, .getQuestionComments, .getCommentResponses, @@ -109,7 +122,13 @@ enum DiscussionEndpoint: EndPointType { .createNewThread, .searchThreads: return nil - case .voteThread, .voteResponse, .flagThread, .flagComment, .followThread, .readBody: + case .voteThread, + .voteResponse, + .flagThread, + .flagComment, + .followThread, + .readBody, + .getResponse: return ["Content-Type": "application/merge-patch+json"] } } @@ -166,8 +185,15 @@ enum DiscussionEndpoint: EndPointType { parameters["order_by"] = "vote_count" } return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + case .getThread: + return .requestParameters(parameters: [:], encoding: URLEncoding.queryString) case .getTopics: return .requestParameters(encoding: URLEncoding.queryString) + case let .getTopic(_, topicID): + let parameters: [String: Encodable] = [ + "topic_id": topicID + ] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) case let .getDiscussionComments(threadID, page): let parameters: [String: Encodable] = [ "thread_id": threadID, @@ -189,6 +215,8 @@ enum DiscussionEndpoint: EndPointType { "page": page ] return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + case .getResponse: + return .requestParameters(parameters: [:], encoding: URLEncoding.queryString) case let .addCommentTo(threadID, rawBody, parentID): var parameters: [String: Encodable] = [ "thread_id": threadID, diff --git a/Discussion/Discussion/Data/Network/DiscussionRepository.swift b/Discussion/Discussion/Data/Network/DiscussionRepository.swift index ab8686ad7..a939611e8 100644 --- a/Discussion/Discussion/Data/Network/DiscussionRepository.swift +++ b/Discussion/Discussion/Data/Network/DiscussionRepository.swift @@ -15,11 +15,14 @@ public protocol DiscussionRepositoryProtocol { sort: SortType, filter: ThreadsFilter, page: Int) async throws -> ThreadLists + func getThread(threadID: String) async throws -> UserThread func searchThreads(courseID: String, searchText: String, pageNumber: Int) async throws -> ThreadLists func getTopics(courseID: String) async throws -> Topics + func getTopic(courseID: String, topicID: String) async throws -> Topics func getDiscussionComments(threadID: String, page: Int) async throws -> ([UserComment], Pagination) func getQuestionComments(threadID: String, page: Int) async throws -> ([UserComment], Pagination) func getCommentResponses(commentID: String, page: Int) async throws -> ([UserComment], Pagination) + func getResponse(responseID: String) async throws -> UserComment func addCommentTo(threadID: String, rawBody: String, parentID: String?) async throws -> Post func voteThread(voted: Bool, threadID: String) async throws func voteResponse(voted: Bool, responseID: String) async throws @@ -57,7 +60,14 @@ public class DiscussionRepository: DiscussionRepositoryProtocol { return try await renameThreadUser(data: threads).domain } - + + public func getThread(threadID: String) async throws -> UserThread { + let thread = try await api.requestData(DiscussionEndpoint + .getThread(threadID: threadID)) + .mapResponse(DataLayer.ThreadList.self) + return thread.userThread + } + public func searchThreads(courseID: String, searchText: String, pageNumber: Int) async throws -> ThreadLists { let posts = try await api.requestData(DiscussionEndpoint.searchThreads(courseID: courseID, searchText: searchText, @@ -71,7 +81,14 @@ public class DiscussionRepository: DiscussionRepositoryProtocol { .mapResponse(DataLayer.TopicsResponse.self) return topics.domain } - + + public func getTopic(courseID: String, topicID: String) async throws -> Topics { + let topic = try await api.requestData(DiscussionEndpoint + .getTopic(courseID: courseID, topicID: topicID)) + .mapResponse(DataLayer.TopicsResponse.self) + return topic.domain + } + public func getDiscussionComments(threadID: String, page: Int) async throws -> ([UserComment], Pagination) { let response = try await api.requestData(DiscussionEndpoint .getDiscussionComments(threadID: threadID, page: page)) @@ -92,7 +109,14 @@ public class DiscussionRepository: DiscussionRepositoryProtocol { let result = try await renameUsers(data: response) return (result.domain, result.pagination.domain) } - + + public func getResponse(responseID: String) async throws -> UserComment { + let response = try await api.requestData(DiscussionEndpoint + .getResponse(responseID: responseID)) + .mapResponse(DataLayer.Comments.self) + return response.domain + } + public func addCommentTo(threadID: String, rawBody: String, parentID: String? = nil) async throws -> Post { let endpoint = DiscussionEndpoint.addCommentTo(threadID: threadID, rawBody: rawBody, parentID: parentID) return try await api.requestData(endpoint).mapResponse(DataLayer.CreatedComment.self).domain @@ -181,6 +205,33 @@ public class DiscussionRepository: DiscussionRepositoryProtocol { #if DEBUG // swiftlint:disable all public class DiscussionRepositoryMock: DiscussionRepositoryProtocol { + + public func getThread(threadID: String) async throws -> UserThread { + UserThread( + id: "", + author: "", + authorLabel: "", + createdAt: Date(), + updatedAt: Date(), + rawBody: "", + renderedBody: "", + voted: true, + voteCount: 1, + courseID: "", + type: .discussion, + title: "", + pinned: true, + closed: true, + following: true, + commentCount: 1, + avatar: "", + unreadCommentCount: 1, + abuseFlagged: true, + hasEndorsed: true, + numPages: 1 + ) + } + var comments = [ UserComment(authorName: "Bill", @@ -328,7 +379,28 @@ public class DiscussionRepositoryMock: DiscussionRepositoryProtocol { ] ) } - + + public func getTopic( + courseID: String, + topicID: String + ) async throws -> Topics { + Topics( + coursewareTopics: + [ + CoursewareTopics(id: "", name: "CourseWare Topics", + threadListURL: "", + children: [CoursewareTopics(id: "", + name: "Child topic", + threadListURL: "", + children: [])]) + ], + nonCoursewareTopics: + [ + CoursewareTopics(id: "", name: "Non Courseware Topics", threadListURL: "", children: []) + ] + ) + } + public func getDiscussionComments(threadID: String, page: Int) async throws -> ([UserComment], Pagination) { (comments, Pagination(next: nil, previous: nil, count: 10, numPages: 1)) } @@ -340,7 +412,11 @@ public class DiscussionRepositoryMock: DiscussionRepositoryProtocol { public func getCommentResponses(commentID: String, page: Int) async throws -> ([UserComment], Pagination) { (comments, Pagination(next: nil, previous: nil, count: 10, numPages: 1)) } - + + public func getResponse(responseID: String) async throws -> UserComment { + UserComment(authorName: "", authorAvatar: "", postDate: Date(), postTitle: "", postBody: "", postBodyHtml: "", postVisible: true, voted: true, followed: true, votesCount: 1, responsesCount: 1, threadID: "", commentID: "", parentID: "", abuseFlagged: true) + } + public func addCommentTo(threadID: String, rawBody: String, parentID: String?) async throws -> Post { Post( authorName: "John", diff --git a/Discussion/Discussion/Domain/DiscussionInteractor.swift b/Discussion/Discussion/Domain/DiscussionInteractor.swift index ee6de6716..4fc92f804 100644 --- a/Discussion/Discussion/Domain/DiscussionInteractor.swift +++ b/Discussion/Discussion/Domain/DiscussionInteractor.swift @@ -16,10 +16,13 @@ public protocol DiscussionInteractorProtocol { filter: ThreadsFilter, page: Int) async throws -> ThreadLists func getTopics(courseID: String) async throws -> Topics + func getTopic(courseID: String, topicID: String) async throws -> Topics func searchThreads(courseID: String, searchText: String, pageNumber: Int) async throws -> ThreadLists + func getThread(threadID: String) async throws -> UserThread func getDiscussionComments(threadID: String, page: Int) async throws -> ([UserComment], Pagination) func getQuestionComments(threadID: String, page: Int) async throws -> ([UserComment], Pagination) func getCommentResponses(commentID: String, page: Int) async throws -> ([UserComment], Pagination) + func getResponse(responseID: String) async throws -> UserComment func addCommentTo(threadID: String, rawBody: String, parentID: String?) async throws -> Post func voteThread(voted: Bool, threadID: String) async throws func voteResponse(voted: Bool, responseID: String) async throws @@ -45,7 +48,11 @@ public class DiscussionInteractor: DiscussionInteractorProtocol { page: Int) async throws -> ThreadLists { return try await repository.getThreads(courseID: courseID, type: type, sort: sort, filter: filter, page: page) } - + + public func getThread(threadID: String) async throws -> UserThread { + return try await repository.getThread(threadID: threadID) + } + public func searchThreads(courseID: String, searchText: String, pageNumber: Int) async throws -> ThreadLists { return try await repository.searchThreads(courseID: courseID, searchText: searchText, pageNumber: pageNumber) } @@ -53,7 +60,11 @@ public class DiscussionInteractor: DiscussionInteractorProtocol { public func getTopics(courseID: String) async throws -> Topics { return try await repository.getTopics(courseID: courseID) } - + + public func getTopic(courseID: String, topicID: String) async throws -> Topics { + return try await repository.getTopic(courseID: courseID, topicID: topicID) + } + public func getDiscussionComments(threadID: String, page: Int) async throws -> ([UserComment], Pagination) { return try await repository.getDiscussionComments(threadID: threadID, page: page) } @@ -65,7 +76,12 @@ public class DiscussionInteractor: DiscussionInteractorProtocol { public func getCommentResponses(commentID: String, page: Int) async throws -> ([UserComment], Pagination) { return try await repository.getCommentResponses(commentID: commentID, page: page) } - + + // TODO: This Api should be updated with type GET, currently we are using this for deep linking on comment screen. + public func getResponse(responseID: String) async throws -> UserComment { + return try await repository.getResponse(responseID: responseID) + } + public func addCommentTo(threadID: String, rawBody: String, parentID: String? = nil) async throws -> Post { return try await repository.addCommentTo(threadID: threadID, rawBody: rawBody, diff --git a/Discussion/Discussion/Domain/Model/UserComment.swift b/Discussion/Discussion/Domain/Model/UserComment.swift index 4fb1e8e39..cdc8ecb92 100644 --- a/Discussion/Discussion/Domain/Model/UserComment.swift +++ b/Discussion/Discussion/Domain/Model/UserComment.swift @@ -45,3 +45,27 @@ public struct UserComment: Hashable { self.abuseFlagged = abuseFlagged } } + +public extension UserComment { + var post: Post { + Post( + authorName: authorName, + authorAvatar: authorAvatar, + postDate: postDate, + postTitle: postTitle, + postBodyHtml: postBodyHtml, + postBody: postBody, + postVisible: postVisible, + voted: voted, + followed: followed, + votesCount: votesCount, + responsesCount: responsesCount, + comments: [], + threadID: threadID, + commentID: commentID, + parentID: parentID, + abuseFlagged: abuseFlagged, + closed: false + ) + } +} diff --git a/Discussion/Discussion/Domain/Model/UserThread.swift b/Discussion/Discussion/Domain/Model/UserThread.swift index 54a03a21d..b87a14a36 100644 --- a/Discussion/Discussion/Domain/Model/UserThread.swift +++ b/Discussion/Discussion/Domain/Model/UserThread.swift @@ -112,3 +112,31 @@ public extension DataLayer.ThreadListsResponse { return ThreadLists(threads: threadsReady) } } + +public extension DataLayer.ThreadList { + var userThread: UserThread { + UserThread( + id: id, + author: author ?? DiscussionLocalization.anonymous, + authorLabel: authorLabel ?? "", + createdAt: Date(iso8601: createdAt), + updatedAt: Date(iso8601: updatedAt), + rawBody: rawBody, + renderedBody: renderedBody, + voted: voted, + voteCount: voteCount, + courseID: courseID, + type: type, + title: title, + pinned: pinned, + closed: closed, + following: following, + commentCount: commentCount, + avatar: users?.userName?.profile?.image?.imageURLLarge ?? "", + unreadCommentCount: unreadCommentCount, + abuseFlagged: abuseFlagged, + hasEndorsed: hasEndorsed, + numPages: 0 + ) + } +} diff --git a/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift b/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift index b004ec6ed..b8bf0167b 100644 --- a/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift +++ b/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift @@ -12,7 +12,7 @@ import Theme public struct ThreadView: View { private var title: String - private let thread: UserThread + public let thread: UserThread private var onBackTapped: (() -> Void) = {} @ObservedObject private var viewModel: ThreadViewModel @@ -123,7 +123,8 @@ public struct ThreadView: View { viewModel.router.showComments( commentID: comment.commentID, parentComment: comment, - threadStateSubject: viewModel.threadStateSubject + threadStateSubject: viewModel.threadStateSubject, + animated: true ) }, onFetchMore: { diff --git a/Discussion/Discussion/Presentation/DiscussionRouter.swift b/Discussion/Discussion/Presentation/DiscussionRouter.swift index 5cc0fed94..4fbdc31ad 100644 --- a/Discussion/Discussion/Presentation/DiscussionRouter.swift +++ b/Discussion/Discussion/Presentation/DiscussionRouter.swift @@ -14,16 +14,17 @@ public protocol DiscussionRouter: BaseRouter { func showUserDetails(username: String) - func showThreads(courseID: String, topics: Topics, title: String, type: ThreadType) - - func showThread(thread: UserThread, postStateSubject: CurrentValueSubject) - + func showThreads(courseID: String, topics: Topics, title: String, type: ThreadType, animated: Bool) + + func showThread(thread: UserThread, postStateSubject: CurrentValueSubject, animated: Bool) + func showDiscussionsSearch(courseID: String) func showComments( commentID: String, parentComment: Post, - threadStateSubject: CurrentValueSubject + threadStateSubject: CurrentValueSubject, + animated: Bool ) func createNewThread(courseID: String, selectedTopic: String, onPostCreated: @escaping () -> Void) @@ -37,16 +38,21 @@ public class DiscussionRouterMock: BaseRouterMock, DiscussionRouter { public func showUserDetails(username: String) {} - public func showThreads(courseID: String, topics: Topics, title: String, type: ThreadType) {} - - public func showThread(thread: UserThread, postStateSubject: CurrentValueSubject) {} + public func showThreads(courseID: String, topics: Topics, title: String, type: ThreadType, animated: Bool) {} + public func showThread( + thread: UserThread, + postStateSubject: CurrentValueSubject, + animated: Bool + ) {} + public func showDiscussionsSearch(courseID: String) {} public func showComments( commentID: String, parentComment: Post, - threadStateSubject: CurrentValueSubject + threadStateSubject: CurrentValueSubject, + animated: Bool ) {} public func createNewThread( diff --git a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionSearchTopicsViewModel.swift b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionSearchTopicsViewModel.swift index a8025c28c..928e4b028 100644 --- a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionSearchTopicsViewModel.swift +++ b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionSearchTopicsViewModel.swift @@ -159,7 +159,11 @@ public class DiscussionSearchTopicsViewModel: ObservableObject { for thread in threads { result.append(thread.discussionPost(action: { [weak self] in guard let self else { return } - self.router.showThread(thread: thread, postStateSubject: self.postStateSubject) + self.router.showThread( + thread: thread, + postStateSubject: self.postStateSubject, + animated: true + ) })) } return result diff --git a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift index f61e5dc84..9aa950084 100644 --- a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift +++ b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift @@ -9,6 +9,7 @@ import Foundation import SwiftUI import Core +// swiftlint:disable function_body_length public class DiscussionTopicsViewModel: ObservableObject { @Published var topics: Topics? @@ -54,7 +55,9 @@ public class DiscussionTopicsViewModel: ObservableObject { courseID: self.courseID, topics: topics ?? Topics(coursewareTopics: [], nonCoursewareTopics: []), title: DiscussionLocalization.Topics.allPosts, - type: .allPosts) + type: .allPosts, + animated: true + ) }, style: .basic ), @@ -66,7 +69,8 @@ public class DiscussionTopicsViewModel: ObservableObject { courseID: self.courseID, topics: topics ?? Topics(coursewareTopics: [], nonCoursewareTopics: []), title: DiscussionLocalization.Topics.postImFollowing, - type: .followingPosts + type: .followingPosts, + animated: true ) }, style: .followed) @@ -87,8 +91,10 @@ public class DiscussionTopicsViewModel: ObservableObject { courseID: self.courseID, topics: topics, title: t.name, - type: .nonCourseTopics) - + type: .nonCourseTopics, + animated: true + ) + }, style: .basic) ) @@ -107,7 +113,8 @@ public class DiscussionTopicsViewModel: ObservableObject { courseID: self.courseID, topics: topics, title: t.name, - type: .nonCourseTopics + type: .nonCourseTopics, + animated: true ) }, style: .subTopic) @@ -136,7 +143,8 @@ public class DiscussionTopicsViewModel: ObservableObject { courseID: self.courseID, topics: topics, title: child.name, - type: .courseTopics(topicID: child.id) + type: .courseTopics(topicID: child.id), + animated: true ) }, style: .subTopic) @@ -165,3 +173,4 @@ public class DiscussionTopicsViewModel: ObservableObject { } } } +// swiftlint:enable function_body_length diff --git a/Discussion/Discussion/Presentation/Posts/PostsView.swift b/Discussion/Discussion/Presentation/Posts/PostsView.swift index 5b3da4cfb..f599b111d 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsView.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsView.swift @@ -51,7 +51,18 @@ public struct PostsView: View { self.showTopMenu = true self.viewModel.courseID = courseID } - + + public var topicID: String? { + if case .courseTopics(let topicID) = viewModel.type { + return topicID + } + return nil + } + + public var type: ThreadType? { + viewModel.type + } + public var body: some View { ZStack(alignment: .top) { // MARK: - Page Body diff --git a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift index 04b02c788..af567009c 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift @@ -158,7 +158,11 @@ public class PostsViewModel: ObservableObject { guard let self, let actualThread = self.threads.threads .first(where: {$0.id == thread.id }) else { return } - self.router.showThread(thread: actualThread, postStateSubject: self.postStateSubject) + self.router.showThread( + thread: actualThread, + postStateSubject: self.postStateSubject, + animated: true + ) })) } } diff --git a/Discussion/DiscussionTests/DiscussionMock.generated.swift b/Discussion/DiscussionTests/DiscussionMock.generated.swift index 336d50a14..2202fd248 100644 --- a/Discussion/DiscussionTests/DiscussionMock.generated.swift +++ b/Discussion/DiscussionTests/DiscussionMock.generated.swift @@ -599,10 +599,10 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } @@ -622,7 +622,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -707,9 +707,10 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -733,7 +734,7 @@ open class BaseRouterMock: BaseRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { @@ -753,7 +754,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -787,7 +788,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -839,8 +840,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } } @@ -1405,6 +1406,22 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock return __value } + open func getTopic(courseID: String, topicID: String) throws -> Topics { + addInvocation(.m_getTopic__courseID_courseIDtopicID_topicID(Parameter.value(`courseID`), Parameter.value(`topicID`))) + let perform = methodPerformValue(.m_getTopic__courseID_courseIDtopicID_topicID(Parameter.value(`courseID`), Parameter.value(`topicID`))) as? (String, String) -> Void + perform?(`courseID`, `topicID`) + var __value: Topics + do { + __value = try methodReturnValue(.m_getTopic__courseID_courseIDtopicID_topicID(Parameter.value(`courseID`), Parameter.value(`topicID`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getTopic(courseID: String, topicID: String). Use given") + Failure("Stub return value not specified for getTopic(courseID: String, topicID: String). Use given") + } catch { + throw error + } + return __value + } + open func searchThreads(courseID: String, searchText: String, pageNumber: Int) throws -> ThreadLists { addInvocation(.m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(Parameter.value(`courseID`), Parameter.value(`searchText`), Parameter.value(`pageNumber`))) let perform = methodPerformValue(.m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(Parameter.value(`courseID`), Parameter.value(`searchText`), Parameter.value(`pageNumber`))) as? (String, String, Int) -> Void @@ -1421,6 +1438,22 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock return __value } + open func getThread(threadID: String) throws -> UserThread { + addInvocation(.m_getThread__threadID_threadID(Parameter.value(`threadID`))) + let perform = methodPerformValue(.m_getThread__threadID_threadID(Parameter.value(`threadID`))) as? (String) -> Void + perform?(`threadID`) + var __value: UserThread + do { + __value = try methodReturnValue(.m_getThread__threadID_threadID(Parameter.value(`threadID`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getThread(threadID: String). Use given") + Failure("Stub return value not specified for getThread(threadID: String). Use given") + } catch { + throw error + } + return __value + } + open func getDiscussionComments(threadID: String, page: Int) throws -> ([UserComment], Pagination) { addInvocation(.m_getDiscussionComments__threadID_threadIDpage_page(Parameter.value(`threadID`), Parameter.value(`page`))) let perform = methodPerformValue(.m_getDiscussionComments__threadID_threadIDpage_page(Parameter.value(`threadID`), Parameter.value(`page`))) as? (String, Int) -> Void @@ -1469,6 +1502,22 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock return __value } + open func getResponse(responseID: String) throws -> UserComment { + addInvocation(.m_getResponse__responseID_responseID(Parameter.value(`responseID`))) + let perform = methodPerformValue(.m_getResponse__responseID_responseID(Parameter.value(`responseID`))) as? (String) -> Void + perform?(`responseID`) + var __value: UserComment + do { + __value = try methodReturnValue(.m_getResponse__responseID_responseID(Parameter.value(`responseID`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for getResponse(responseID: String). Use given") + Failure("Stub return value not specified for getResponse(responseID: String). Use given") + } catch { + throw error + } + return __value + } + open func addCommentTo(threadID: String, rawBody: String, parentID: String?) throws -> Post { addInvocation(.m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(Parameter.value(`threadID`), Parameter.value(`rawBody`), Parameter.value(`parentID`))) let perform = methodPerformValue(.m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(Parameter.value(`threadID`), Parameter.value(`rawBody`), Parameter.value(`parentID`))) as? (String, String, String?) -> Void @@ -1580,10 +1629,13 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock fileprivate enum MethodType { case m_getThreadsList__courseID_courseIDtype_typesort_sortfilter_filterpage_page(Parameter, Parameter, Parameter, Parameter, Parameter) case m_getTopics__courseID_courseID(Parameter) + case m_getTopic__courseID_courseIDtopicID_topicID(Parameter, Parameter) case m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(Parameter, Parameter, Parameter) + case m_getThread__threadID_threadID(Parameter) case m_getDiscussionComments__threadID_threadIDpage_page(Parameter, Parameter) case m_getQuestionComments__threadID_threadIDpage_page(Parameter, Parameter) case m_getCommentResponses__commentID_commentIDpage_page(Parameter, Parameter) + case m_getResponse__responseID_responseID(Parameter) case m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(Parameter, Parameter, Parameter) case m_voteThread__voted_votedthreadID_threadID(Parameter, Parameter) case m_voteResponse__voted_votedresponseID_responseID(Parameter, Parameter) @@ -1609,6 +1661,12 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) + case (.m_getTopic__courseID_courseIDtopicID_topicID(let lhsCourseid, let lhsTopicid), .m_getTopic__courseID_courseIDtopicID_topicID(let rhsCourseid, let rhsTopicid)): + 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: lhsTopicid, rhs: rhsTopicid, with: matcher), lhsTopicid, rhsTopicid, "topicID")) + return Matcher.ComparisonResult(results) + case (.m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(let lhsCourseid, let lhsSearchtext, let lhsPagenumber), .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(let rhsCourseid, let rhsSearchtext, let rhsPagenumber)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) @@ -1616,6 +1674,11 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPagenumber, rhs: rhsPagenumber, with: matcher), lhsPagenumber, rhsPagenumber, "pageNumber")) return Matcher.ComparisonResult(results) + case (.m_getThread__threadID_threadID(let lhsThreadid), .m_getThread__threadID_threadID(let rhsThreadid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsThreadid, rhs: rhsThreadid, with: matcher), lhsThreadid, rhsThreadid, "threadID")) + return Matcher.ComparisonResult(results) + case (.m_getDiscussionComments__threadID_threadIDpage_page(let lhsThreadid, let lhsPage), .m_getDiscussionComments__threadID_threadIDpage_page(let rhsThreadid, let rhsPage)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsThreadid, rhs: rhsThreadid, with: matcher), lhsThreadid, rhsThreadid, "threadID")) @@ -1634,6 +1697,11 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPage, rhs: rhsPage, with: matcher), lhsPage, rhsPage, "page")) return Matcher.ComparisonResult(results) + case (.m_getResponse__responseID_responseID(let lhsResponseid), .m_getResponse__responseID_responseID(let rhsResponseid)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsResponseid, rhs: rhsResponseid, with: matcher), lhsResponseid, rhsResponseid, "responseID")) + return Matcher.ComparisonResult(results) + case (.m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(let lhsThreadid, let lhsRawbody, let lhsParentid), .m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(let rhsThreadid, let rhsRawbody, let rhsParentid)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsThreadid, rhs: rhsThreadid, with: matcher), lhsThreadid, rhsThreadid, "threadID")) @@ -1688,10 +1756,13 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock switch self { case let .m_getThreadsList__courseID_courseIDtype_typesort_sortfilter_filterpage_page(p0, p1, p2, p3, p4): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue case let .m_getTopics__courseID_courseID(p0): return p0.intValue + case let .m_getTopic__courseID_courseIDtopicID_topicID(p0, p1): return p0.intValue + p1.intValue case let .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + case let .m_getThread__threadID_threadID(p0): return p0.intValue case let .m_getDiscussionComments__threadID_threadIDpage_page(p0, p1): return p0.intValue + p1.intValue case let .m_getQuestionComments__threadID_threadIDpage_page(p0, p1): return p0.intValue + p1.intValue case let .m_getCommentResponses__commentID_commentIDpage_page(p0, p1): return p0.intValue + p1.intValue + case let .m_getResponse__responseID_responseID(p0): return p0.intValue case let .m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue case let .m_voteThread__voted_votedthreadID_threadID(p0, p1): return p0.intValue + p1.intValue case let .m_voteResponse__voted_votedresponseID_responseID(p0, p1): return p0.intValue + p1.intValue @@ -1706,10 +1777,13 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock switch self { case .m_getThreadsList__courseID_courseIDtype_typesort_sortfilter_filterpage_page: return ".getThreadsList(courseID:type:sort:filter:page:)" case .m_getTopics__courseID_courseID: return ".getTopics(courseID:)" + case .m_getTopic__courseID_courseIDtopicID_topicID: return ".getTopic(courseID:topicID:)" case .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber: return ".searchThreads(courseID:searchText:pageNumber:)" + case .m_getThread__threadID_threadID: return ".getThread(threadID:)" case .m_getDiscussionComments__threadID_threadIDpage_page: return ".getDiscussionComments(threadID:page:)" case .m_getQuestionComments__threadID_threadIDpage_page: return ".getQuestionComments(threadID:page:)" case .m_getCommentResponses__commentID_commentIDpage_page: return ".getCommentResponses(commentID:page:)" + case .m_getResponse__responseID_responseID: return ".getResponse(responseID:)" case .m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID: return ".addCommentTo(threadID:rawBody:parentID:)" case .m_voteThread__voted_votedthreadID_threadID: return ".voteThread(voted:threadID:)" case .m_voteResponse__voted_votedresponseID_responseID: return ".voteResponse(voted:responseID:)" @@ -1737,9 +1811,15 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock public static func getTopics(courseID: Parameter, willReturn: Topics...) -> MethodStub { return Given(method: .m_getTopics__courseID_courseID(`courseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + public static func getTopic(courseID: Parameter, topicID: Parameter, willReturn: Topics...) -> MethodStub { + return Given(method: .m_getTopic__courseID_courseIDtopicID_topicID(`courseID`, `topicID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func searchThreads(courseID: Parameter, searchText: Parameter, pageNumber: Parameter, willReturn: ThreadLists...) -> MethodStub { return Given(method: .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(`courseID`, `searchText`, `pageNumber`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + public static func getThread(threadID: Parameter, willReturn: UserThread...) -> MethodStub { + return Given(method: .m_getThread__threadID_threadID(`threadID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func getDiscussionComments(threadID: Parameter, page: Parameter, willReturn: ([UserComment], Pagination)...) -> MethodStub { return Given(method: .m_getDiscussionComments__threadID_threadIDpage_page(`threadID`, `page`), products: willReturn.map({ StubProduct.return($0 as Any) })) } @@ -1749,6 +1829,9 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock public static func getCommentResponses(commentID: Parameter, page: Parameter, willReturn: ([UserComment], Pagination)...) -> MethodStub { return Given(method: .m_getCommentResponses__commentID_commentIDpage_page(`commentID`, `page`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + public static func getResponse(responseID: Parameter, willReturn: UserComment...) -> MethodStub { + return Given(method: .m_getResponse__responseID_responseID(`responseID`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func addCommentTo(threadID: Parameter, rawBody: Parameter, parentID: Parameter, willReturn: Post...) -> MethodStub { return Given(method: .m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(`threadID`, `rawBody`, `parentID`), products: willReturn.map({ StubProduct.return($0 as Any) })) } @@ -1772,6 +1855,16 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock willProduce(stubber) return given } + public static func getTopic(courseID: Parameter, topicID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getTopic__courseID_courseIDtopicID_topicID(`courseID`, `topicID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getTopic(courseID: Parameter, topicID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getTopic__courseID_courseIDtopicID_topicID(`courseID`, `topicID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (Topics).self) + willProduce(stubber) + return given + } public static func searchThreads(courseID: Parameter, searchText: Parameter, pageNumber: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(`courseID`, `searchText`, `pageNumber`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -1782,6 +1875,16 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock willProduce(stubber) return given } + public static func getThread(threadID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getThread__threadID_threadID(`threadID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getThread(threadID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getThread__threadID_threadID(`threadID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (UserThread).self) + willProduce(stubber) + return given + } public static func getDiscussionComments(threadID: Parameter, page: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_getDiscussionComments__threadID_threadIDpage_page(`threadID`, `page`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -1812,6 +1915,16 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock willProduce(stubber) return given } + public static func getResponse(responseID: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_getResponse__responseID_responseID(`responseID`), products: willThrow.map({ StubProduct.throw($0) })) + } + public static func getResponse(responseID: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_getResponse__responseID_responseID(`responseID`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (UserComment).self) + willProduce(stubber) + return given + } public static func addCommentTo(threadID: Parameter, rawBody: Parameter, parentID: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(`threadID`, `rawBody`, `parentID`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -1899,10 +2012,13 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock public static func getThreadsList(courseID: Parameter, type: Parameter, sort: Parameter, filter: Parameter, page: Parameter) -> Verify { return Verify(method: .m_getThreadsList__courseID_courseIDtype_typesort_sortfilter_filterpage_page(`courseID`, `type`, `sort`, `filter`, `page`))} public static func getTopics(courseID: Parameter) -> Verify { return Verify(method: .m_getTopics__courseID_courseID(`courseID`))} + public static func getTopic(courseID: Parameter, topicID: Parameter) -> Verify { return Verify(method: .m_getTopic__courseID_courseIDtopicID_topicID(`courseID`, `topicID`))} public static func searchThreads(courseID: Parameter, searchText: Parameter, pageNumber: Parameter) -> Verify { return Verify(method: .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(`courseID`, `searchText`, `pageNumber`))} + public static func getThread(threadID: Parameter) -> Verify { return Verify(method: .m_getThread__threadID_threadID(`threadID`))} public static func getDiscussionComments(threadID: Parameter, page: Parameter) -> Verify { return Verify(method: .m_getDiscussionComments__threadID_threadIDpage_page(`threadID`, `page`))} public static func getQuestionComments(threadID: Parameter, page: Parameter) -> Verify { return Verify(method: .m_getQuestionComments__threadID_threadIDpage_page(`threadID`, `page`))} public static func getCommentResponses(commentID: Parameter, page: Parameter) -> Verify { return Verify(method: .m_getCommentResponses__commentID_commentIDpage_page(`commentID`, `page`))} + public static func getResponse(responseID: Parameter) -> Verify { return Verify(method: .m_getResponse__responseID_responseID(`responseID`))} public static func addCommentTo(threadID: Parameter, rawBody: Parameter, parentID: Parameter) -> Verify { return Verify(method: .m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(`threadID`, `rawBody`, `parentID`))} public static func voteThread(voted: Parameter, threadID: Parameter) -> Verify { return Verify(method: .m_voteThread__voted_votedthreadID_threadID(`voted`, `threadID`))} public static func voteResponse(voted: Parameter, responseID: Parameter) -> Verify { return Verify(method: .m_voteResponse__voted_votedresponseID_responseID(`voted`, `responseID`))} @@ -1923,9 +2039,15 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock public static func getTopics(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_getTopics__courseID_courseID(`courseID`), performs: perform) } + public static func getTopic(courseID: Parameter, topicID: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_getTopic__courseID_courseIDtopicID_topicID(`courseID`, `topicID`), performs: perform) + } public static func searchThreads(courseID: Parameter, searchText: Parameter, pageNumber: Parameter, perform: @escaping (String, String, Int) -> Void) -> Perform { return Perform(method: .m_searchThreads__courseID_courseIDsearchText_searchTextpageNumber_pageNumber(`courseID`, `searchText`, `pageNumber`), performs: perform) } + public static func getThread(threadID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getThread__threadID_threadID(`threadID`), performs: perform) + } public static func getDiscussionComments(threadID: Parameter, page: Parameter, perform: @escaping (String, Int) -> Void) -> Perform { return Perform(method: .m_getDiscussionComments__threadID_threadIDpage_page(`threadID`, `page`), performs: perform) } @@ -1935,6 +2057,9 @@ open class DiscussionInteractorProtocolMock: DiscussionInteractorProtocol, Mock public static func getCommentResponses(commentID: Parameter, page: Parameter, perform: @escaping (String, Int) -> Void) -> Perform { return Perform(method: .m_getCommentResponses__commentID_commentIDpage_page(`commentID`, `page`), performs: perform) } + public static func getResponse(responseID: Parameter, perform: @escaping (String) -> Void) -> Perform { + return Perform(method: .m_getResponse__responseID_responseID(`responseID`), performs: perform) + } public static func addCommentTo(threadID: Parameter, rawBody: Parameter, parentID: Parameter, perform: @escaping (String, String, String?) -> Void) -> Perform { return Perform(method: .m_addCommentTo__threadID_threadIDrawBody_rawBodyparentID_parentID(`threadID`, `rawBody`, `parentID`), performs: perform) } @@ -2084,16 +2209,16 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { perform?(`username`) } - open func showThreads(courseID: String, topics: Topics, title: String, type: ThreadType) { - addInvocation(.m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type(Parameter.value(`courseID`), Parameter.value(`topics`), Parameter.value(`title`), Parameter.value(`type`))) - let perform = methodPerformValue(.m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type(Parameter.value(`courseID`), Parameter.value(`topics`), Parameter.value(`title`), Parameter.value(`type`))) as? (String, Topics, String, ThreadType) -> Void - perform?(`courseID`, `topics`, `title`, `type`) + open func showThreads(courseID: String, topics: Topics, title: String, type: ThreadType, animated: Bool) { + addInvocation(.m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated(Parameter.value(`courseID`), Parameter.value(`topics`), Parameter.value(`title`), Parameter.value(`type`), Parameter.value(`animated`))) + let perform = methodPerformValue(.m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated(Parameter.value(`courseID`), Parameter.value(`topics`), Parameter.value(`title`), Parameter.value(`type`), Parameter.value(`animated`))) as? (String, Topics, String, ThreadType, Bool) -> Void + perform?(`courseID`, `topics`, `title`, `type`, `animated`) } - open func showThread(thread: UserThread, postStateSubject: CurrentValueSubject) { - addInvocation(.m_showThread__thread_threadpostStateSubject_postStateSubject(Parameter.value(`thread`), Parameter>.value(`postStateSubject`))) - let perform = methodPerformValue(.m_showThread__thread_threadpostStateSubject_postStateSubject(Parameter.value(`thread`), Parameter>.value(`postStateSubject`))) as? (UserThread, CurrentValueSubject) -> Void - perform?(`thread`, `postStateSubject`) + open func showThread(thread: UserThread, postStateSubject: CurrentValueSubject, animated: Bool) { + addInvocation(.m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated(Parameter.value(`thread`), Parameter>.value(`postStateSubject`), Parameter.value(`animated`))) + let perform = methodPerformValue(.m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated(Parameter.value(`thread`), Parameter>.value(`postStateSubject`), Parameter.value(`animated`))) as? (UserThread, CurrentValueSubject, Bool) -> Void + perform?(`thread`, `postStateSubject`, `animated`) } open func showDiscussionsSearch(courseID: String) { @@ -2102,10 +2227,10 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { perform?(`courseID`) } - open func showComments(commentID: String, parentComment: Post, threadStateSubject: CurrentValueSubject) { - addInvocation(.m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject(Parameter.value(`commentID`), Parameter.value(`parentComment`), Parameter>.value(`threadStateSubject`))) - let perform = methodPerformValue(.m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject(Parameter.value(`commentID`), Parameter.value(`parentComment`), Parameter>.value(`threadStateSubject`))) as? (String, Post, CurrentValueSubject) -> Void - perform?(`commentID`, `parentComment`, `threadStateSubject`) + open func showComments(commentID: String, parentComment: Post, threadStateSubject: CurrentValueSubject, animated: Bool) { + addInvocation(.m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated(Parameter.value(`commentID`), Parameter.value(`parentComment`), Parameter>.value(`threadStateSubject`), Parameter.value(`animated`))) + let perform = methodPerformValue(.m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated(Parameter.value(`commentID`), Parameter.value(`parentComment`), Parameter>.value(`threadStateSubject`), Parameter.value(`animated`))) as? (String, Post, CurrentValueSubject, Bool) -> Void + perform?(`commentID`, `parentComment`, `threadStateSubject`, `animated`) } open func createNewThread(courseID: String, selectedTopic: String, onPostCreated: @escaping () -> Void) { @@ -2204,19 +2329,19 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } fileprivate enum MethodType { case m_showUserDetails__username_username(Parameter) - case m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type(Parameter, Parameter, Parameter, Parameter) - case m_showThread__thread_threadpostStateSubject_postStateSubject(Parameter, Parameter>) + case m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated(Parameter, Parameter, Parameter, Parameter, Parameter) + case m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated(Parameter, Parameter>, Parameter) case m_showDiscussionsSearch__courseID_courseID(Parameter) - case m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject(Parameter, Parameter, Parameter>) + case m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated(Parameter, Parameter, Parameter>, Parameter) case m_createNewThread__courseID_courseIDselectedTopic_selectedTopiconPostCreated_onPostCreated(Parameter, Parameter, Parameter<() -> Void>) case m_backToRoot__animated_animated(Parameter) case m_back__animated_animated(Parameter) @@ -2233,7 +2358,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -2242,18 +2367,20 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsUsername, rhs: rhsUsername, with: matcher), lhsUsername, rhsUsername, "username")) return Matcher.ComparisonResult(results) - case (.m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type(let lhsCourseid, let lhsTopics, let lhsTitle, let lhsType), .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type(let rhsCourseid, let rhsTopics, let rhsTitle, let rhsType)): + case (.m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated(let lhsCourseid, let lhsTopics, let lhsTitle, let lhsType, let lhsAnimated), .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated(let rhsCourseid, let rhsTopics, let rhsTitle, let rhsType, let rhsAnimated)): 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: lhsTopics, rhs: rhsTopics, with: matcher), lhsTopics, rhsTopics, "topics")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTitle, rhs: rhsTitle, with: matcher), lhsTitle, rhsTitle, "title")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsType, rhs: rhsType, with: matcher), lhsType, rhsType, "type")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) return Matcher.ComparisonResult(results) - case (.m_showThread__thread_threadpostStateSubject_postStateSubject(let lhsThread, let lhsPoststatesubject), .m_showThread__thread_threadpostStateSubject_postStateSubject(let rhsThread, let rhsPoststatesubject)): + case (.m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated(let lhsThread, let lhsPoststatesubject, let lhsAnimated), .m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated(let rhsThread, let rhsPoststatesubject, let rhsAnimated)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsThread, rhs: rhsThread, with: matcher), lhsThread, rhsThread, "thread")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPoststatesubject, rhs: rhsPoststatesubject, with: matcher), lhsPoststatesubject, rhsPoststatesubject, "postStateSubject")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) return Matcher.ComparisonResult(results) case (.m_showDiscussionsSearch__courseID_courseID(let lhsCourseid), .m_showDiscussionsSearch__courseID_courseID(let rhsCourseid)): @@ -2261,11 +2388,12 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCourseid, rhs: rhsCourseid, with: matcher), lhsCourseid, rhsCourseid, "courseID")) return Matcher.ComparisonResult(results) - case (.m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject(let lhsCommentid, let lhsParentcomment, let lhsThreadstatesubject), .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject(let rhsCommentid, let rhsParentcomment, let rhsThreadstatesubject)): + case (.m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated(let lhsCommentid, let lhsParentcomment, let lhsThreadstatesubject, let lhsAnimated), .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated(let rhsCommentid, let rhsParentcomment, let rhsThreadstatesubject, let rhsAnimated)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsCommentid, rhs: rhsCommentid, with: matcher), lhsCommentid, rhsCommentid, "commentID")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsParentcomment, rhs: rhsParentcomment, with: matcher), lhsParentcomment, rhsParentcomment, "parentComment")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsThreadstatesubject, rhs: rhsThreadstatesubject, with: matcher), lhsThreadstatesubject, rhsThreadstatesubject, "threadStateSubject")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) return Matcher.ComparisonResult(results) case (.m_createNewThread__courseID_courseIDselectedTopic_selectedTopiconPostCreated_onPostCreated(let lhsCourseid, let lhsSelectedtopic, let lhsOnpostcreated), .m_createNewThread__courseID_courseIDselectedTopic_selectedTopiconPostCreated_onPostCreated(let rhsCourseid, let rhsSelectedtopic, let rhsOnpostcreated)): @@ -2356,9 +2484,10 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -2368,10 +2497,10 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { func intValue() -> Int { switch self { case let .m_showUserDetails__username_username(p0): return p0.intValue - case let .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type(p0, p1, p2, p3): return p0.intValue + p1.intValue + p2.intValue + p3.intValue - case let .m_showThread__thread_threadpostStateSubject_postStateSubject(p0, p1): return p0.intValue + p1.intValue + case let .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated(p0, p1, p2, p3, p4): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + case let .m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue case let .m_showDiscussionsSearch__courseID_courseID(p0): return p0.intValue - case let .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue + case let .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated(p0, p1, p2, p3): return p0.intValue + p1.intValue + p2.intValue + p3.intValue case let .m_createNewThread__courseID_courseIDselectedTopic_selectedTopiconPostCreated_onPostCreated(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue case let .m_backToRoot__animated_animated(p0): return p0.intValue case let .m_back__animated_animated(p0): return p0.intValue @@ -2388,16 +2517,16 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { switch self { case .m_showUserDetails__username_username: return ".showUserDetails(username:)" - case .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type: return ".showThreads(courseID:topics:title:type:)" - case .m_showThread__thread_threadpostStateSubject_postStateSubject: return ".showThread(thread:postStateSubject:)" + case .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated: return ".showThreads(courseID:topics:title:type:animated:)" + case .m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated: return ".showThread(thread:postStateSubject:animated:)" case .m_showDiscussionsSearch__courseID_courseID: return ".showDiscussionsSearch(courseID:)" - case .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject: return ".showComments(commentID:parentComment:threadStateSubject:)" + case .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated: return ".showComments(commentID:parentComment:threadStateSubject:animated:)" case .m_createNewThread__courseID_courseIDselectedTopic_selectedTopiconPostCreated_onPostCreated: return ".createNewThread(courseID:selectedTopic:onPostCreated:)" case .m_backToRoot__animated_animated: return ".backToRoot(animated:)" case .m_back__animated_animated: return ".back(animated:)" @@ -2414,7 +2543,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -2434,10 +2563,10 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { fileprivate var method: MethodType public static func showUserDetails(username: Parameter) -> Verify { return Verify(method: .m_showUserDetails__username_username(`username`))} - public static func showThreads(courseID: Parameter, topics: Parameter, title: Parameter, type: Parameter) -> Verify { return Verify(method: .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type(`courseID`, `topics`, `title`, `type`))} - public static func showThread(thread: Parameter, postStateSubject: Parameter>) -> Verify { return Verify(method: .m_showThread__thread_threadpostStateSubject_postStateSubject(`thread`, `postStateSubject`))} + public static func showThreads(courseID: Parameter, topics: Parameter, title: Parameter, type: Parameter, animated: Parameter) -> Verify { return Verify(method: .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated(`courseID`, `topics`, `title`, `type`, `animated`))} + public static func showThread(thread: Parameter, postStateSubject: Parameter>, animated: Parameter) -> Verify { return Verify(method: .m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated(`thread`, `postStateSubject`, `animated`))} public static func showDiscussionsSearch(courseID: Parameter) -> Verify { return Verify(method: .m_showDiscussionsSearch__courseID_courseID(`courseID`))} - public static func showComments(commentID: Parameter, parentComment: Parameter, threadStateSubject: Parameter>) -> Verify { return Verify(method: .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject(`commentID`, `parentComment`, `threadStateSubject`))} + public static func showComments(commentID: Parameter, parentComment: Parameter, threadStateSubject: Parameter>, animated: Parameter) -> Verify { return Verify(method: .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated(`commentID`, `parentComment`, `threadStateSubject`, `animated`))} public static func createNewThread(courseID: Parameter, selectedTopic: Parameter, onPostCreated: Parameter<() -> Void>) -> Verify { return Verify(method: .m_createNewThread__courseID_courseIDselectedTopic_selectedTopiconPostCreated_onPostCreated(`courseID`, `selectedTopic`, `onPostCreated`))} public static func backToRoot(animated: Parameter) -> Verify { return Verify(method: .m_backToRoot__animated_animated(`animated`))} public static func back(animated: Parameter) -> Verify { return Verify(method: .m_back__animated_animated(`animated`))} @@ -2454,7 +2583,7 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -2464,17 +2593,17 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { public static func showUserDetails(username: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_showUserDetails__username_username(`username`), performs: perform) } - public static func showThreads(courseID: Parameter, topics: Parameter, title: Parameter, type: Parameter, perform: @escaping (String, Topics, String, ThreadType) -> Void) -> Perform { - return Perform(method: .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_type(`courseID`, `topics`, `title`, `type`), performs: perform) + public static func showThreads(courseID: Parameter, topics: Parameter, title: Parameter, type: Parameter, animated: Parameter, perform: @escaping (String, Topics, String, ThreadType, Bool) -> Void) -> Perform { + return Perform(method: .m_showThreads__courseID_courseIDtopics_topicstitle_titletype_typeanimated_animated(`courseID`, `topics`, `title`, `type`, `animated`), performs: perform) } - public static func showThread(thread: Parameter, postStateSubject: Parameter>, perform: @escaping (UserThread, CurrentValueSubject) -> Void) -> Perform { - return Perform(method: .m_showThread__thread_threadpostStateSubject_postStateSubject(`thread`, `postStateSubject`), performs: perform) + public static func showThread(thread: Parameter, postStateSubject: Parameter>, animated: Parameter, perform: @escaping (UserThread, CurrentValueSubject, Bool) -> Void) -> Perform { + return Perform(method: .m_showThread__thread_threadpostStateSubject_postStateSubjectanimated_animated(`thread`, `postStateSubject`, `animated`), performs: perform) } public static func showDiscussionsSearch(courseID: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_showDiscussionsSearch__courseID_courseID(`courseID`), performs: perform) } - public static func showComments(commentID: Parameter, parentComment: Parameter, threadStateSubject: Parameter>, perform: @escaping (String, Post, CurrentValueSubject) -> Void) -> Perform { - return Perform(method: .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubject(`commentID`, `parentComment`, `threadStateSubject`), performs: perform) + public static func showComments(commentID: Parameter, parentComment: Parameter, threadStateSubject: Parameter>, animated: Parameter, perform: @escaping (String, Post, CurrentValueSubject, Bool) -> Void) -> Perform { + return Perform(method: .m_showComments__commentID_commentIDparentComment_parentCommentthreadStateSubject_threadStateSubjectanimated_animated(`commentID`, `parentComment`, `threadStateSubject`, `animated`), performs: perform) } public static func createNewThread(courseID: Parameter, selectedTopic: Parameter, onPostCreated: Parameter<() -> Void>, perform: @escaping (String, String, @escaping () -> Void) -> Void) -> Perform { return Perform(method: .m_createNewThread__courseID_courseIDselectedTopic_selectedTopiconPostCreated_onPostCreated(`courseID`, `selectedTopic`, `onPostCreated`), performs: perform) @@ -2524,8 +2653,8 @@ open class DiscussionRouterMock: DiscussionRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } } diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index b1557b530..043a37fbf 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ A5BD3E302B83B0F7006A8983 /* SegmentFirebase in Frameworks */ = {isa = PBXBuildFile; productRef = A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */; }; A5C10D8F2B861A70008E864D /* SegmentAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C10D8E2B861A70008E864D /* SegmentAnalyticsService.swift */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; + BA7468762B96201D00793145 /* DeepLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7468752B96201D00793145 /* DeepLinkRouter.swift */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -142,6 +143,7 @@ A59702282B83C87900CA064C /* FirebaseAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAnalyticsService.swift; sourceTree = ""; }; A5C10D8E2B861A70008E864D /* SegmentAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentAnalyticsService.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; + BA7468752B96201D00793145 /* DeepLinkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkRouter.swift; sourceTree = ""; }; BB08ACD2CCA33D6DDDDD31B4 /* Pods-App-OpenEdX.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasedev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasedev.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F138C15C3A2515F8F94DAA8B /* Pods_App_OpenEdX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_OpenEdX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -332,6 +334,7 @@ A59568942B61630500ED4F90 /* DeepLinkManager.swift */, A5F46FD02B692B140003EEEF /* Services */, A59585AD2B62677B00A35A20 /* Link */, + BA7468742B96200500793145 /* DeepLinkRouter */, ); path = DeepLinkManager; sourceTree = ""; @@ -369,6 +372,14 @@ path = Services; sourceTree = ""; }; + BA7468742B96200500793145 /* DeepLinkRouter */ = { + isa = PBXGroup; + children = ( + BA7468752B96201D00793145 /* DeepLinkRouter.swift */, + ); + path = DeepLinkRouter; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -554,6 +565,7 @@ 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, 02F175312A4DA95B0019CD70 /* MainScreenAnalytics.swift in Sources */, + BA7468762B96201D00793145 /* DeepLinkRouter.swift in Sources */, 0727878E28D347C7002E9142 /* MainScreenView.swift in Sources */, 0770DE5028D0A707006D8A5D /* NetworkAssembly.swift in Sources */, 0293A2032A6FCA590090A336 /* CorePersistence.swift in Sources */, diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 2c65937c8..8077b4d8d 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -172,7 +172,13 @@ class AppAssembly: Assembly { container.register(DeepLinkManager.self) { r in DeepLinkManager( - config: r.resolve(ConfigProtocol.self)! + config: r.resolve(ConfigProtocol.self)!, + router: r.resolve(Router.self)!, + storage: r.resolve(CoreStorage.self)!, + discoveryInteractor: r.resolve(DiscoveryInteractorProtocol.self)!, + discussionInteractor: r.resolve(DiscussionInteractorProtocol.self)!, + courseInteractor: r.resolve(CourseInteractorProtocol.self)!, + profileInteractor: r.resolve(ProfileInteractorProtocol.self)! ) }.inObjectScope(.container) diff --git a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift index 677188fee..58382005d 100644 --- a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift +++ b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift @@ -8,7 +8,13 @@ import Foundation import Core import UIKit +import Discovery +import Discussion +import Course +import Profile +// swiftlint:disable function_body_length type_body_length +//sourcery: AutoMockable public protocol DeepLinkService { func configureWith( manager: DeepLinkManager, @@ -26,9 +32,34 @@ public protocol DeepLinkService { public class DeepLinkManager { private var services: [DeepLinkService] = [] private let config: ConfigProtocol - - public init(config: ConfigProtocol) { + private let storage: CoreStorage + private let router: DeepLinkRouter + private let discoveryInteractor: DiscoveryInteractorProtocol + private let discussionInteractor: DiscussionInteractorProtocol + private let courseInteractor: CourseInteractorProtocol + private let profileInteractor: ProfileInteractorProtocol + + var userloggedIn: Bool { + return !(storage.user?.username?.isEmpty ?? true) + } + + public init( + config: ConfigProtocol, + router: DeepLinkRouter, + storage: CoreStorage, + discoveryInteractor: DiscoveryInteractorProtocol, + discussionInteractor: DiscussionInteractorProtocol, + courseInteractor: CourseInteractorProtocol, + profileInteractor: ProfileInteractorProtocol + ) { self.config = config + self.router = router + self.storage = storage + self.discoveryInteractor = discoveryInteractor + self.discussionInteractor = discussionInteractor + self.courseInteractor = courseInteractor + self.profileInteractor = profileInteractor + services = servicesFor(config: config) } @@ -68,16 +99,305 @@ public class DeepLinkManager { // This method do redirect with link from push notification func processLinkFromNotification(_ link: PushLink) { // redirect if possible + guard link.type != .none else { + return + } + + let isAppActive = UIApplication.shared.applicationState == .active + + Task { + if isAppActive { + await showNotificationAlert(link) + } else { + await navigateToScreen(with: link.type, link: link) + } + } } // This method process the deep link with response parameters func processDeepLink(with params: [AnyHashable: Any]?) { guard let params = params else { return } - let deeplink = DeepLink(dictionary: params) if anyServiceEnabled && deeplink.type != .none { - // redirect if possible + Task { + await navigateToScreen(with: deeplink.type, link: deeplink) + } + } + } + + @MainActor + private func showNotificationAlert(_ link: PushLink) { + router.dismissPresentedViewController() + + router.presentAlert( + alertTitle: link.title ?? "", + alertMessage: link.body ?? "", + positiveAction: CoreLocalization.Alert.accept, + onCloseTapped: { [weak self] in + self?.router.dismiss(animated: true) + }, + okTapped: { [weak self] in + guard let self else { + return + } + Task { + self.router.dismiss(animated: true) + await self.navigateToScreen(with: link.type, link: link) + } + }, + type: .deepLink + ) + } + + private func isDiscovery(type: DeepLinkType) -> Bool { + type == .discovery || + type == .discoveryCourseDetail || + type == .discoveryProgramDetail + } + + private func isDiscussionThreads(type: DeepLinkType) -> Bool { + type == .discussionPost || + type == .discussionTopic || + type == .discussionComment + } + + private func isHandout(type: DeepLinkType) -> Bool { + type == .courseHandout || + type == .courseAnnouncement + } + + @MainActor + private func navigateToScreen( + with type: DeepLinkType, + link: DeepLink + ) async { + if isDiscovery(type: type) { + showDiscoveryScreen(with: type, link: link) + return + } + + if !userloggedIn { + router.backToRoot(animated: true) + router.showLoginScreen(sourceScreen: .default) + return + } + + switch type { + case .courseDashboard, + .courseVideos, + .discussions, + .courseDates, + .courseHandout, + .courseAnnouncement, + .discussionTopic, + .discussionPost, + .discussionComment: + await showCourseScreen(with: type, link: link) + case .program, .programDetail: + guard config.program.enabled else { return } + if let pathID = link.pathID, !pathID.isEmpty { + router.showProgram(pathID: pathID) + return + } + router.showTabScreen(tab: .programs) + case .profile: + router.showTabScreen(tab: .profile) + case .userProfile: + await showEditProfile() + default: + break + } + } + + private func showDiscoveryScreen(with type: DeepLinkType, link: DeepLink) { + switch type { + case .discovery: + if userloggedIn { + router.showTabScreen(tab: .discovery) + return + } + router.showDiscovery() + case .discoveryCourseDetail, .discoveryProgramDetail: + guard let pathID = link.pathID ?? link.courseID, !pathID.isEmpty else { + router.showTabScreen(tab: .discovery) + return + } + router.showDiscoveryDetails(link: link, pathID: pathID) + default: + break + } + } + + @MainActor + private func showCourseScreen(with type: DeepLinkType, link: DeepLink) async { + guard let courseID = link.courseID, !courseID.isEmpty else { + return + } + + do { + let courseDetails = try await discoveryInteractor.getCourseDetails( + courseID: courseID + ) + + router.showCourseDetail( + link: link, + courseDetails: courseDetails + ) { [weak self] in + guard let self else { + return + } + + if self.isHandout(type: type) { + self.router.showProgress() + Task { + do { + try await self.showHandout(link: link, courseDetails: courseDetails) + self.router.dismissProgress() + } catch { + self.router.dismissProgress() + } + } + return + } + + if self.isDiscussionThreads(type: type) { + self.router.showProgress() + Task { + await self.showCourseDiscussion(link: link, courseDetails: courseDetails) + self.router.dismissProgress() + } + return + } + } + } catch { + router.dismissProgress() + } + } + + @MainActor + private func showHandout( + link: DeepLink, + courseDetails: CourseDetails + ) async throws { + switch link.type { + case .courseAnnouncement: + let updates = try await courseInteractor.getUpdates(courseID: courseDetails.courseID) + if updates.isEmpty { + throw DeepLinkError.error(text: CoreLocalization.Error.unknownError) + } + router.showAnnouncement(courseDetails: courseDetails, updates: updates) + case .courseHandout: + let handouts = try await courseInteractor.getHandouts(courseID: courseDetails.courseID) + router.showHandout(courseDetails: courseDetails, handouts: handouts) + default: + break + } + } + + @MainActor + private func showCourseDiscussion( + link: DeepLink, + courseDetails: CourseDetails + ) async { + switch link.type { + case .discussionTopic: + guard let topicID = link.topicID, + !topicID.isEmpty else { + return + } + + guard let topics = try? await discussionInteractor.getTopic( + courseID: courseDetails.courseID, + topicID: topicID + ) else { + return + } + + router.showThreads( + topicID: topicID, + courseDetails: courseDetails, + topics: topics + ) + case .discussionPost: + + if let topicID = link.topicID, + !topicID.isEmpty, + let topics = try? await discussionInteractor.getTopic( + courseID: courseDetails.courseID, + topicID: topicID + ) { + router.showThreads( + topicID: topicID, + courseDetails: courseDetails, + topics: topics + ) + } + + if let threadID = link.threadID, + !threadID.isEmpty, + let userThread = try? await discussionInteractor.getThread(threadID: threadID) { + router.showThread( + userThread: userThread + ) + } + + case .discussionComment: + if let topicID = link.topicID, + !topicID.isEmpty, + let topics = try? await discussionInteractor.getTopic( + courseID: courseDetails.courseID, + topicID: topicID + ) { + router.showThreads( + topicID: topicID, + courseDetails: courseDetails, + topics: topics + ) + } + + if let threadID = link.threadID, + !threadID.isEmpty, + let userThread = try? await discussionInteractor.getThread(threadID: threadID) { + router.showThread( + userThread: userThread + ) + } + + if let commentID = link.commentID, + !commentID.isEmpty, + let comment = try? await self.discussionInteractor.getResponse(responseID: commentID), + let parentID = comment.parentID, + !parentID.isEmpty, + let parentComment = try? await self.discussionInteractor.getResponse(responseID: parentID) { + router.showComment( + comment: comment, + parentComment: parentComment.post + ) + } + default: + break + } + } + + @MainActor + private func showEditProfile() async { + guard let userProfile = try? await profileInteractor.getMyProfile() else { + return + } + router.showUserProfile(userProfile: userProfile) + } +} + +public enum DeepLinkError: Error { + case error(text: String) +} + +extension DeepLinkError: LocalizedError { + public var errorDescription: String? { + switch self { + case .error(let text): + return text } } - } +// swiftlint:enable function_body_length type_body_length diff --git a/OpenEdX/Managers/DeepLinkManager/DeepLinkRouter/DeepLinkRouter.swift b/OpenEdX/Managers/DeepLinkManager/DeepLinkRouter/DeepLinkRouter.swift new file mode 100644 index 000000000..48a37bd20 --- /dev/null +++ b/OpenEdX/Managers/DeepLinkManager/DeepLinkRouter/DeepLinkRouter.swift @@ -0,0 +1,357 @@ +// +// DeepLinkRouter.swift +// OpenEdX +// +// Created by Eugene Yatsenko on 04.03.2024. +// + +import Core +import SwiftUI +import Theme +import Discovery +import Discussion +import Course +import Profile + +public protocol DeepLinkRouter: BaseRouter { + func showTabScreen(tab: MainTab) + func showDiscovery() + func showDiscoveryDetails( + link: DeepLink, + pathID: String + ) + func showCourseDetail( + link: DeepLink, + courseDetails: CourseDetails, + completion: @escaping () -> Void + ) + func showAnnouncement( + courseDetails: CourseDetails, + updates: [CourseUpdate] + ) + func showHandout( + courseDetails: CourseDetails, + handouts: String? + ) + func showThreads( + topicID: String, + courseDetails: CourseDetails, + topics: Topics + ) + func showThread( + userThread: UserThread + ) + func showComment( + comment: UserComment, + parentComment: Post + ) + func showProgram( + pathID: String + ) + func showUserProfile(userProfile: UserProfile) + func dismissPresentedViewController() + func showProgress() + func dismissProgress() +} + +extension Router: DeepLinkRouter { + + // MARK: - DeepLinkRouter + + public func showDiscoveryDetails( + link: DeepLink, + pathID: String + ) { + switch link.type { + case .discoveryCourseDetail: + if hostDiscoveryWebview?.rootView.pathID == pathID { + return + } + showTabScreen(tab: .discovery) + showWebDiscoveryDetails( + pathID: pathID, + discoveryType: .courseDetail(pathID), + sourceScreen: .discovery + ) + case .discoveryProgramDetail: + if hostProgramWebviewView?.rootView.pathID == pathID { + return + } + showTabScreen(tab: .discovery) + showWebProgramDetails( + pathID: pathID, + viewType: .programDetail + ) + default: + break + } + } + + public func showCourseDetail( + link: DeepLink, + courseDetails: CourseDetails, + completion: @escaping () -> Void + ) { + let isCourseOpened = hostCourseContainerView?.rootView.courseID == courseDetails.courseID + + if !isCourseOpened { + showTabScreen(tab: .dashboard) + + if courseDetails.isEnrolled { + showCourseScreens( + courseID: courseDetails.courseID, + isActive: nil, + courseStart: courseDetails.courseStart, + courseEnd: courseDetails.courseEnd, + enrollmentStart: courseDetails.enrollmentStart, + enrollmentEnd: courseDetails.enrollmentEnd, + title: courseDetails.courseTitle + ) + } else { + showCourseDetais( + courseID: courseDetails.courseID, + title: courseDetails.courseTitle + ) + } + } + + switch link.type { + case .courseVideos, + .courseDates, + .discussions, + .courseHandout, + .courseAnnouncement, + .courseDashboard: + popToCourseContainerView(animated: false) + default: + break + } + + DispatchQueue.main.asyncAfter(deadline: .now() + (isCourseOpened ? 0 : 1)) { + switch link.type { + case .courseDashboard: + self.hostCourseContainerView?.rootView.viewModel.selection = CourseTab.course.rawValue + case .courseVideos: + self.hostCourseContainerView?.rootView.viewModel.selection = CourseTab.videos.rawValue + case .courseDates: + self.hostCourseContainerView?.rootView.viewModel.selection = CourseTab.dates.rawValue + case .discussions, .discussionTopic, .discussionPost, .discussionComment: + self.hostCourseContainerView?.rootView.viewModel.selection = CourseTab.discussion.rawValue + case .courseHandout, .courseAnnouncement: + self.hostCourseContainerView?.rootView.viewModel.selection = CourseTab.handounds.rawValue + default: + break + } + + completion() + } + } + + public func showAnnouncement( + courseDetails: CourseDetails, + updates: [CourseUpdate] + ) { + guard let cssInjector = container.resolve(CSSInjector.self) else { + return + } + + showHandoutsUpdatesView( + handouts: nil, + announcements: updates, + router: self, + cssInjector: cssInjector + ) + } + + public func showHandout( + courseDetails: CourseDetails, + handouts: String? + ) { + guard let cssInjector = container.resolve(CSSInjector.self) else { + return + } + + showHandoutsUpdatesView( + handouts: handouts, + announcements: nil, + router: self, + cssInjector: cssInjector + ) + } + + public func showProgram( + pathID: String + ) { + if hostProgramWebviewView?.rootView.pathID == pathID { + return + } + showTabScreen(tab: .programs) + showWebProgramDetails( + pathID: pathID, + viewType: .programDetail + ) + } + + public func showThreads( + topicID: String, + courseDetails: CourseDetails, + topics: Topics + ) { + popToCourseContainerView() + + let title = (topics.coursewareTopics.map {$0} + topics.nonCoursewareTopics.map {$0}).first?.name ?? "" + showThreads( + courseID: courseDetails.courseID, + topics: topics, + title: title, + type: .courseTopics(topicID: topicID), + animated: false + ) + } + + public func showThread( + userThread: UserThread + ) { + showThread( + thread: userThread, + postStateSubject: .init(.none), + animated: false + ) + } + + public func showComment( + comment: UserComment, + parentComment: Post + ) { + showComments( + commentID: comment.commentID, + parentComment: parentComment, + threadStateSubject: .init(.none), + animated: false + ) + } + + public func showTabScreen(tab: MainTab) { + dismiss() + hostMainScreen?.rootView.viewModel.select(tab: tab) + } + + public func showDiscovery() { + dismiss() + showDiscoveryScreen(sourceScreen: .startup) + } + + public func showUserProfile(userProfile: UserProfile) { + showTabScreen(tab: .profile) + showEditProfile(userModel: userProfile, avatar: nil) { _, _ in + NotificationCenter.default.post( + name: .profileUpdated, + object: nil + ) + } + + } + + public func showProgress() { + presentView( + transitionStyle: .crossDissolve, + animated: false + ) { + FullScreenProgressView() + } + } + + public func dismissProgress() { + if let presentedViewController = getNavigationController() + .presentedViewController as? UIHostingController { + presentedViewController.dismiss(animated: true) + } + } + + public func dismissPresentedViewController() { + if let presentedViewController = getNavigationController().presentedViewController { + presentedViewController.dismiss(animated: true) + } + } + + private func popToCourseContainerView(animated: Bool = false) { + guard let hostCourseContainerView = hostCourseContainerView else { + return + } + getNavigationController().popToViewController( + hostCourseContainerView, + animated: animated + ) + } + + private var hostMainScreen: UIHostingController? { + getNavigationController().viewControllers.firstAs(UIHostingController.self) + } + + private var hostCourseContainerView: UIHostingController? { + getNavigationController().viewControllers.firstAs(UIHostingController.self) + } + + private var hostHandoutsUpdatesDetailView: UIHostingController? { + getNavigationController().topViewController as? UIHostingController + } + + private var hostDiscoveryWebview: UIHostingController? { + getNavigationController().topViewController as? UIHostingController + } + + private var hostProgramWebviewView: UIHostingController? { + getNavigationController().topViewController as? UIHostingController + } + + private func dismiss() { + dismissPresentedViewController() + backToRoot(animated: false) + } + +} + +// Mark - For testing and SwiftUI preview +#if DEBUG +public class DeepLinkRouterMock: BaseRouterMock, DeepLinkRouter { + public override init() {} + public func showTabScreen(tab: MainTab) {} + public func showDiscovery() {} + public func showDiscoveryDetails( + link: DeepLink, + pathID: String + ) {} + public func showCourseDetail( + link: DeepLink, + courseDetails: CourseDetails, + completion: @escaping () -> Void + ) {} + public func showAnnouncement( + courseDetails: CourseDetails, + updates: [CourseUpdate] + ) {} + public func showHandout( + courseDetails: CourseDetails, + handouts: String? + ) {} + public func showThreads( + topicID: String, + courseDetails: CourseDetails, + topics: Topics + ) {} + public func showThread( + userThread: UserThread + ) {} + public func showComment( + comment: UserComment, + parentComment: Post + ) {} + public func showProgram( + pathID: String + ) {} + public func showUserProfile(userProfile: UserProfile) {} + public func dismissPresentedViewController() {} + public func showProgress() {} + public func dismissProgress() {} +} +#endif diff --git a/OpenEdX/Managers/DeepLinkManager/Link/DeepLink.swift b/OpenEdX/Managers/DeepLinkManager/Link/DeepLink.swift index 8f7f3a407..1da67d716 100644 --- a/OpenEdX/Managers/DeepLinkManager/Link/DeepLink.swift +++ b/OpenEdX/Managers/DeepLinkManager/Link/DeepLink.swift @@ -50,13 +50,13 @@ public class DeepLink { var type: DeepLinkType init(dictionary: [AnyHashable: Any]) { - courseID = dictionary[DeepLinkKeys.courseID] as? String - screenName = dictionary[DeepLinkKeys.screenName] as? String - pathID = dictionary[DeepLinkKeys.pathID] as? String - topicID = dictionary[DeepLinkKeys.topicID] as? String - threadID = dictionary[DeepLinkKeys.threadID] as? String - commentID = dictionary[DeepLinkKeys.commentID] as? String - componentID = dictionary[DeepLinkKeys.componentID] as? String + courseID = dictionary[DeepLinkKeys.courseID.rawValue] as? String + screenName = dictionary[DeepLinkKeys.screenName.rawValue] as? String + pathID = dictionary[DeepLinkKeys.pathID.rawValue] as? String + topicID = dictionary[DeepLinkKeys.topicID.rawValue] as? String + threadID = dictionary[DeepLinkKeys.threadID.rawValue] as? String + commentID = dictionary[DeepLinkKeys.commentID.rawValue] as? String + componentID = dictionary[DeepLinkKeys.componentID.rawValue] as? String type = DeepLinkType(rawValue: screenName ?? DeepLinkType.none.rawValue) ?? .none } } diff --git a/OpenEdX/Managers/DeepLinkManager/Link/PushLink.swift b/OpenEdX/Managers/DeepLinkManager/Link/PushLink.swift index 64d792b1a..2707f82d2 100644 --- a/OpenEdX/Managers/DeepLinkManager/Link/PushLink.swift +++ b/OpenEdX/Managers/DeepLinkManager/Link/PushLink.swift @@ -8,11 +8,12 @@ import Foundation import Core -enum DataKeys: String, RawStringExtractable { +enum DataKeys: String { case title case body case aps case alert + case ab } // This link will have information of course and screen type which will be use to route on particular screen. @@ -21,10 +22,10 @@ public class PushLink: DeepLink { let body: String? override init(dictionary: [AnyHashable: Any]) { - let aps = dictionary[DataKeys.aps] as? [String: Any] - let alert = aps?[DataKeys.alert] as? [String: Any] - title = alert?[DataKeys.title] as? String - body = alert?[DataKeys.body] as? String + let aps = dictionary[DataKeys.aps.rawValue] as? [String: Any] + let alert = aps?[DataKeys.alert.rawValue] as? [String: Any] + title = alert?[DataKeys.title.rawValue] as? String + body = alert?[DataKeys.body.rawValue] as? String super.init(dictionary: dictionary) } diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index 839bba866..8e9667458 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -18,7 +18,8 @@ import Dashboard import Profile import WhatsNew import Combine - + +// swiftlint:disable file_length type_body_length public class Router: AuthorizationRouter, WhatsNewRouter, DiscoveryRouter, @@ -26,16 +27,20 @@ public class Router: AuthorizationRouter, DashboardRouter, CourseRouter, DiscussionRouter { - + public var container: Container - + private let navigationController: UINavigationController - + + func getNavigationController() -> UINavigationController { + navigationController + } + init(navigationController: UINavigationController, container: Container) { self.navigationController = navigationController self.container = container } - + public func backToRoot(animated: Bool) { navigationController.popToRootViewController(animated: animated) } @@ -143,7 +148,10 @@ public class Router: AuthorizationRouter, okTapped: @escaping () -> Void, type: AlertViewType ) { - presentView(transitionStyle: .crossDissolve, content: { + presentView( + transitionStyle: .crossDissolve, + animated: true + ) { AlertView( alertTitle: alertTitle, alertMessage: alertMessage, @@ -152,7 +160,7 @@ public class Router: AuthorizationRouter, okTapped: okTapped, type: type ) - }) + } } public func presentAlert( @@ -165,7 +173,10 @@ public class Router: AuthorizationRouter, okTapped: @escaping () -> Void, nextSectionTapped: @escaping () -> Void ) { - presentView(transitionStyle: .crossDissolve, content: { + presentView( + transitionStyle: .crossDissolve, + animated: true + ) { AlertView( alertTitle: alertTitle, alertMessage: alertMessage, @@ -176,14 +187,14 @@ public class Router: AuthorizationRouter, okTapped: okTapped, nextSectionTapped: { nextSectionTapped() } ) - }) + } } public func presentView(transitionStyle: UIModalTransitionStyle, view: any View) { present(transitionStyle: transitionStyle, view: view) } - public func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { + public func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { let view = prepareToPresent(content(), transitionStyle: transitionStyle) navigationController.present(view, animated: true) } @@ -486,7 +497,13 @@ public class Router: AuthorizationRouter, navigationController.setViewControllers(controllers, animated: animated) } - public func showThreads(courseID: String, topics: Topics, title: String, type: ThreadType) { + public func showThreads( + courseID: String, + topics: Topics, + title: String, + type: ThreadType, + animated: Bool + ) { let router = Container.shared.resolve(DiscussionRouter.self)! let viewModel = Container.shared.resolve(PostsViewModel.self)! let view = PostsView( @@ -499,20 +516,25 @@ public class Router: AuthorizationRouter, router: router ) let controller = UIHostingController(rootView: view) - navigationController.pushViewController(controller, animated: true) + navigationController.pushViewController(controller, animated: animated) } - public func showThread(thread: UserThread, postStateSubject: CurrentValueSubject) { + public func showThread( + thread: UserThread, + postStateSubject: CurrentValueSubject, + animated: Bool + ) { let viewModel = Container.shared.resolve(ThreadViewModel.self, argument: postStateSubject)! let view = ThreadView(thread: thread, viewModel: viewModel) let controller = UIHostingController(rootView: view) - navigationController.pushViewController(controller, animated: true) + navigationController.pushViewController(controller, animated: animated) } public func showComments( commentID: String, parentComment: Post, - threadStateSubject: CurrentValueSubject + threadStateSubject: CurrentValueSubject, + animated: Bool ) { let router = Container.shared.resolve(DiscussionRouter.self)! let viewModel = Container.shared.resolve(ResponsesViewModel.self, argument: threadStateSubject)! @@ -523,7 +545,7 @@ public class Router: AuthorizationRouter, parentComment: parentComment ) let controller = UIHostingController(rootView: view) - navigationController.pushViewController(controller, animated: true) + navigationController.pushViewController(controller, animated: animated) } public func createNewThread( @@ -552,7 +574,7 @@ public class Router: AuthorizationRouter, navigationController.pushViewController(controller, animated: true) } - public func showEditProfile( + public func showEditProfile( userModel: Core.UserProfile, avatar: UIImage?, profileDidEdit: @escaping ((UserProfile?, UIImage?)) -> Void @@ -646,3 +668,4 @@ public class Router: AuthorizationRouter, navigationController.pushViewController(controller, animated: true) } } +// swiftlint:enable file_length type_body_length diff --git a/OpenEdX/View/MainScreenView.swift b/OpenEdX/View/MainScreenView.swift index b3895ab1d..fa4c7c4f8 100644 --- a/OpenEdX/View/MainScreenView.swift +++ b/OpenEdX/View/MainScreenView.swift @@ -17,20 +17,12 @@ import Theme struct MainScreenView: View { - @State private var selection: MainTab = .discovery @State private var settingsTapped: Bool = false @State private var disableAllTabs: Bool = false @State private var updateAvaliable: Bool = false - enum MainTab { - case discovery - case dashboard - case programs - case profile - } - - @ObservedObject private var viewModel: MainScreenViewModel - + @ObservedObject private(set) var viewModel: MainScreenViewModel + init(viewModel: MainScreenViewModel) { self.viewModel = viewModel UITabBar.appearance().isTranslucent = false @@ -45,7 +37,7 @@ struct MainScreenView: View { } var body: some View { - TabView(selection: $selection) { + TabView(selection: $viewModel.selection) { let config = Container.shared.resolve(ConfigProtocol.self) if config?.discovery.enabled ?? false { ZStack { @@ -133,7 +125,7 @@ struct MainScreenView: View { .navigationTitle(titleBar()) .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: { - if selection == .profile { + if viewModel.selection == .profile { Button(action: { settingsTapped.toggle() }, label: { @@ -147,18 +139,18 @@ struct MainScreenView: View { }) } .onReceive(NotificationCenter.default.publisher(for: .onAppUpgradeAccountSettingsTapped)) { _ in - selection = .profile + viewModel.selection = .profile disableAllTabs = true } .onReceive(NotificationCenter.default.publisher(for: .onNewVersionAvaliable)) { _ in updateAvaliable = true } - .onChange(of: selection) { _ in + .onChange(of: viewModel.selection) { _ in if disableAllTabs { - selection = .profile + viewModel.selection = .profile } } - .onChange(of: selection, perform: { selection in + .onChange(of: viewModel.selection, perform: { selection in switch selection { case .discovery: viewModel.trackMainDiscoveryTabClicked() @@ -179,7 +171,7 @@ struct MainScreenView: View { } private func titleBar() -> String { - switch selection { + switch viewModel.selection { case .discovery: return DiscoveryLocalization.title case .dashboard: diff --git a/OpenEdX/View/MainScreenViewModel.swift b/OpenEdX/View/MainScreenViewModel.swift index 1ee8beb7e..d3409b191 100644 --- a/OpenEdX/View/MainScreenViewModel.swift +++ b/OpenEdX/View/MainScreenViewModel.swift @@ -9,13 +9,22 @@ import Foundation import Core import Profile -class MainScreenViewModel: ObservableObject { - +public enum MainTab { + case discovery + case dashboard + case programs + case profile +} + +final class MainScreenViewModel: ObservableObject { + private let analytics: MainScreenAnalytics let config: ConfigProtocol let profileInteractor: ProfileInteractorProtocol var sourceScreen: LogistrationSourceScreen - + + @Published var selection: MainTab = .dashboard + init(analytics: MainScreenAnalytics, config: ConfigProtocol, profileInteractor: ProfileInteractorProtocol, @@ -26,7 +35,11 @@ class MainScreenViewModel: ObservableObject { self.profileInteractor = profileInteractor self.sourceScreen = sourceScreen } - + + public func select(tab: MainTab) { + selection = tab + } + func trackMainDiscoveryTabClicked() { analytics.mainDiscoveryTabClicked() } @@ -39,7 +52,7 @@ class MainScreenViewModel: ObservableObject { func trackMainProfileTabClicked() { analytics.mainProfileTabClicked() } - + @MainActor func prefetchDataForOffline() async { if profileInteractor.getMyProfileOffline() == nil { diff --git a/Profile/Profile/Presentation/EditProfile/EditProfileView.swift b/Profile/Profile/Presentation/EditProfile/EditProfileView.swift index 56914aeff..e4dec0b33 100644 --- a/Profile/Profile/Presentation/EditProfile/EditProfileView.swift +++ b/Profile/Profile/Presentation/EditProfile/EditProfileView.swift @@ -11,11 +11,9 @@ import Theme public struct EditProfileView: View { - @ObservedObject private var viewModel: EditProfileViewModel + @ObservedObject public var viewModel: EditProfileViewModel @State private var showingImagePicker = false @State private var showingBottomSheet = false - private var oldAvatar: UIImage? - private var profileDidEdit: ((UserProfile?, UIImage?)) -> Void public init( viewModel: EditProfileViewModel, @@ -23,9 +21,9 @@ public struct EditProfileView: View { profileDidEdit: @escaping ((UserProfile?, UIImage?)) -> Void ) { self.viewModel = viewModel - self.profileDidEdit = profileDidEdit + self.viewModel.profileDidEdit = profileDidEdit self.viewModel.inputImage = avatar - self.oldAvatar = avatar + self.viewModel.oldAvatar = avatar self.viewModel.loadLocationsAndSpokenLanguages() } @@ -54,7 +52,6 @@ public struct EditProfileView: View { }.offset(x: 36, y: 50) ) }) - .disabled(!viewModel.isEditable) .accessibilityIdentifier("change_profile_image_button") Text(viewModel.userModel.name) @@ -151,11 +148,6 @@ public struct EditProfileView: View { }) .onRightSwipeGesture { viewModel.backButtonTapped() - if viewModel.profileChanges.isAvatarSaved { - self.profileDidEdit((viewModel.editedProfile, viewModel.inputImage)) - } else { - self.profileDidEdit((viewModel.editedProfile, oldAvatar)) - } } .scrollAvoidKeyboard(dismissKeyboardByTap: true) .frameLimit(sizePortrait: 420) @@ -249,13 +241,6 @@ public struct EditProfileView: View { Theme.Colors.background .ignoresSafeArea() ) - .onDisappear { - if viewModel.profileChanges.isAvatarSaved { - self.profileDidEdit((viewModel.editedProfile, viewModel.inputImage)) - } else { - self.profileDidEdit((viewModel.editedProfile, oldAvatar)) - } - } } } diff --git a/Profile/Profile/Presentation/EditProfile/EditProfileViewModel.swift b/Profile/Profile/Presentation/EditProfile/EditProfileViewModel.swift index daa72ab39..b134a7cd4 100644 --- a/Profile/Profile/Presentation/EditProfile/EditProfileViewModel.swift +++ b/Profile/Profile/Presentation/EditProfile/EditProfileViewModel.swift @@ -9,6 +9,7 @@ import Foundation import Core import SwiftUI +// swiftlint:disable file_length type_body_length public struct Changes: Equatable { public var shortBiography: String public var profileType: ProfileType @@ -23,7 +24,10 @@ public class EditProfileViewModel: ObservableObject { @Published private(set) var selectedCountry: PickerItem? @Published private(set) var selectedSpokeLanguage: PickerItem? @Published private(set) var selectedYearOfBirth: PickerItem? - + + var profileDidEdit: (((UserProfile?, UIImage?)) -> Void)? + var oldAvatar: UIImage? + private let currentYear = Calendar.current.component(.year, from: Date()) public let profileTypes: [ProfileType] = [.full, .limited] private var years: [PickerFields.Option] = [] @@ -214,6 +218,12 @@ public class EditProfileViewModel: ObservableObject { parameters["bio"] = self.profileChanges.shortBiography } await uploadData(parameters: parameters) + + if profileChanges.isAvatarSaved { + profileDidEdit?((editedProfile, inputImage)) + } else { + profileDidEdit?((editedProfile, oldAvatar)) + } } @MainActor @@ -353,3 +363,4 @@ public class EditProfileViewModel: ObservableObject { analytics.profileEditDoneClicked() } } +// swiftlint:enable file_length type_body_length diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index c1e3499c0..2a158b211 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -87,6 +87,11 @@ public struct ProfileView: View { Theme.Colors.background .ignoresSafeArea() ) + .onReceive(NotificationCenter.default.publisher(for: .profileUpdated)) { _ in + Task { + await viewModel.getMyProfile() + } + } } private var progressBar: some View { @@ -216,7 +221,10 @@ public struct ProfileView: View { private var logOutButton: some View { VStack { Button(action: { - viewModel.router.presentView(transitionStyle: .crossDissolve) { + viewModel.router.presentView( + transitionStyle: .crossDissolve, + animated: true + ) { AlertView( alertTitle: ProfileLocalization.LogoutAlert.title, alertMessage: ProfileLocalization.LogoutAlert.text, diff --git a/Profile/Profile/Presentation/Profile/ProfileViewModel.swift b/Profile/Profile/Presentation/Profile/ProfileViewModel.swift index a502bd138..6fba1b825 100644 --- a/Profile/Profile/Presentation/Profile/ProfileViewModel.swift +++ b/Profile/Profile/Presentation/Profile/ProfileViewModel.swift @@ -96,7 +96,7 @@ public class ProfileViewModel: ObservableObject { } @MainActor - func getMyProfile(withProgress: Bool = true) async { + public func getMyProfile(withProgress: Bool = true) async { do { let userModel = interactor.getMyProfileOffline() if userModel == nil && connectivity.isInternetAvaliable { @@ -127,7 +127,7 @@ public class ProfileViewModel: ObservableObject { router.showStartupScreen() analytics.userLogout(force: false) } - + func trackProfileVideoSettingsClicked() { analytics.profileVideoSettingsClicked() } diff --git a/Profile/Profile/Presentation/Profile/UserProfile/UserProfileView.swift b/Profile/Profile/Presentation/Profile/UserProfile/UserProfileView.swift index 905f03681..fb9718b20 100644 --- a/Profile/Profile/Presentation/Profile/UserProfile/UserProfileView.swift +++ b/Profile/Profile/Presentation/Profile/UserProfile/UserProfileView.swift @@ -12,10 +12,14 @@ import Theme public struct UserProfileView: View { + @Environment(\.dismiss) private var dismiss @ObservedObject private var viewModel: UserProfileViewModel - - public init(viewModel: UserProfileViewModel) { + + public var isSheet: Bool + + public init(viewModel: UserProfileViewModel, isSheet: Bool = false) { self.viewModel = viewModel + self.isSheet = isSheet } public var body: some View { @@ -101,6 +105,10 @@ public struct UserProfileView: View { await viewModel.getUserProfile() } } + .sheetNavigation(isSheet: isSheet) { + dismiss() + } + } } diff --git a/Profile/ProfileTests/ProfileMock.generated.swift b/Profile/ProfileTests/ProfileMock.generated.swift index 19f41b288..614d436b5 100644 --- a/Profile/ProfileTests/ProfileMock.generated.swift +++ b/Profile/ProfileTests/ProfileMock.generated.swift @@ -599,10 +599,10 @@ open class BaseRouterMock: BaseRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } @@ -622,7 +622,7 @@ open class BaseRouterMock: BaseRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -707,9 +707,10 @@ open class BaseRouterMock: BaseRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -733,7 +734,7 @@ open class BaseRouterMock: BaseRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { @@ -753,7 +754,7 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -787,7 +788,7 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -839,8 +840,8 @@ open class BaseRouterMock: BaseRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } } @@ -2743,10 +2744,10 @@ open class ProfileRouterMock: ProfileRouter, Mock { perform?(`transitionStyle`, `view`) } - open func presentView(transitionStyle: UIModalTransitionStyle, content: () -> any View) { - addInvocation(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) - let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStylecontent_content(Parameter.value(`transitionStyle`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, () -> any View) -> Void - perform?(`transitionStyle`, `content`) + open func presentView(transitionStyle: UIModalTransitionStyle, animated: Bool, content: () -> any View) { + addInvocation(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) + let perform = methodPerformValue(.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter.value(`transitionStyle`), Parameter.value(`animated`), Parameter<() -> any View>.any)) as? (UIModalTransitionStyle, Bool, () -> any View) -> Void + perform?(`transitionStyle`, `animated`, `content`) } @@ -2771,7 +2772,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) - case m_presentView__transitionStyle_transitionStylecontent_content(Parameter, Parameter<() -> any View>) + case m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(Parameter, Parameter, Parameter<() -> any View>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { @@ -2878,9 +2879,10 @@ open class ProfileRouterMock: ProfileRouter, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsView, rhs: rhsView, with: matcher), lhsView, rhsView, "view")) return Matcher.ComparisonResult(results) - case (.m_presentView__transitionStyle_transitionStylecontent_content(let lhsTransitionstyle, let lhsContent), .m_presentView__transitionStyle_transitionStylecontent_content(let rhsTransitionstyle, let rhsContent)): + case (.m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let lhsTransitionstyle, let lhsAnimated, let lhsContent), .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(let rhsTransitionstyle, let rhsAnimated, let rhsContent)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsTransitionstyle, rhs: rhsTransitionstyle, with: matcher), lhsTransitionstyle, rhsTransitionstyle, "transitionStyle")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAnimated, rhs: rhsAnimated, with: matcher), lhsAnimated, rhsAnimated, "animated")) results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsContent, rhs: rhsContent, with: matcher), lhsContent, rhsContent, "content")) return Matcher.ComparisonResult(results) default: return .none @@ -2909,7 +2911,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue - case let .m_presentView__transitionStyle_transitionStylecontent_content(p0, p1): return p0.intValue + p1.intValue + case let .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue } } func assertionName() -> String { @@ -2934,7 +2936,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" - case .m_presentView__transitionStyle_transitionStylecontent_content: return ".presentView(transitionStyle:content:)" + case .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content: return ".presentView(transitionStyle:animated:content:)" } } } @@ -2973,7 +2975,7 @@ open class ProfileRouterMock: ProfileRouter, Mock { public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`))} + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`))} } public struct Perform { @@ -3040,8 +3042,8 @@ open class ProfileRouterMock: ProfileRouter, Mock { public static func presentView(transitionStyle: Parameter, view: Parameter, perform: @escaping (UIModalTransitionStyle, any View) -> Void) -> Perform { return Perform(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`), performs: perform) } - public static func presentView(transitionStyle: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, () -> any View) -> Void) -> Perform { - return Perform(method: .m_presentView__transitionStyle_transitionStylecontent_content(`transitionStyle`, `content`), performs: perform) + public static func presentView(transitionStyle: Parameter, animated: Parameter, content: Parameter<() -> any View>, perform: @escaping (UIModalTransitionStyle, Bool, () -> any View) -> Void) -> Perform { + return Perform(method: .m_presentView__transitionStyle_transitionStyleanimated_animatedcontent_content(`transitionStyle`, `animated`, `content`), performs: perform) } }