From 724c729c255d03bb2e7046bfa95a55edcf96834f Mon Sep 17 00:00:00 2001 From: Yevhen Kyivskyi Date: Mon, 30 Sep 2024 12:00:57 +0200 Subject: [PATCH] Implement EngagementLauncher logic --- GliaWidgets.xcodeproj/project.pbxproj | 16 +++ .../Public/Glia/Glia+EngagementLauncher.swift | 15 ++- .../Public/Glia/Glia+StartEngagement.swift | 99 +++++++++++----- .../EngagementLauncher.swift | 29 +++-- .../EngagementLauncherTests.swift | 50 +++++++++ .../Glia/GliaTests+EngagementLauncher.swift | 106 ++++++++++++++++++ .../Glia/GliaTests+StartEngagement.swift | 1 + 7 files changed, 270 insertions(+), 46 deletions(-) create mode 100644 GliaWidgetsTests/Sources/EngagementLauncher/EngagementLauncherTests.swift create mode 100644 GliaWidgetsTests/Sources/Glia/GliaTests+EngagementLauncher.swift diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index 93dbd7d05..b2120d287 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -167,6 +167,8 @@ 1AFB1E7825F8B26800CA460D /* ChatTextContentStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AFB1E7725F8B26800CA460D /* ChatTextContentStyle.swift */; }; 215A25902CA44D8A0013023E /* Glia+EngagementLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215A258F2CA44D8A0013023E /* Glia+EngagementLauncher.swift */; }; 215A25932CA44D900013023E /* EngagementLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215A25912CA44D900013023E /* EngagementLauncher.swift */; }; + 215A25982CABC7DF0013023E /* EngagementLauncherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215A25972CABC7DF0013023E /* EngagementLauncherTests.swift */; }; + 215A259A2CAC19780013023E /* GliaTests+EngagementLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215A25992CAC19780013023E /* GliaTests+EngagementLauncher.swift */; }; 23D69155F4F4C5043173EF05 /* Pods_GliaWidgets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7A5CDD05FB57D55971AA68A /* Pods_GliaWidgets.framework */; }; 3100D929296E946600DEC9CE /* SecureConversations.ConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3100D924296E946600DEC9CE /* SecureConversations.ConfirmationView.swift */; }; 3100D92A296E946600DEC9CE /* Theme+SecureConversationsConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3100D925296E946600DEC9CE /* Theme+SecureConversationsConfirmation.swift */; }; @@ -1199,6 +1201,8 @@ 1AFB1E7725F8B26800CA460D /* ChatTextContentStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextContentStyle.swift; sourceTree = ""; }; 215A258F2CA44D8A0013023E /* Glia+EngagementLauncher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Glia+EngagementLauncher.swift"; sourceTree = ""; }; 215A25912CA44D900013023E /* EngagementLauncher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EngagementLauncher.swift; sourceTree = ""; }; + 215A25972CABC7DF0013023E /* EngagementLauncherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EngagementLauncherTests.swift; sourceTree = ""; }; + 215A25992CAC19780013023E /* GliaTests+EngagementLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GliaTests+EngagementLauncher.swift"; sourceTree = ""; }; 235300A49A5836A51EB1C4E8 /* Pods-GliaWidgets.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GliaWidgets.release.xcconfig"; path = "Target Support Files/Pods-GliaWidgets/Pods-GliaWidgets.release.xcconfig"; sourceTree = ""; }; 2797F86D83B9055FAD6E596E /* Pods-SnapshotTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SnapshotTests.debug.xcconfig"; path = "Target Support Files/Pods-SnapshotTests/Pods-SnapshotTests.debug.xcconfig"; sourceTree = ""; }; 3100D924296E946600DEC9CE /* SecureConversations.ConfirmationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureConversations.ConfirmationView.swift; sourceTree = ""; }; @@ -3204,6 +3208,14 @@ path = EngagementLauncher; sourceTree = ""; }; + 215A25962CABC7C50013023E /* EngagementLauncher */ = { + isa = PBXGroup; + children = ( + 215A25972CABC7DF0013023E /* EngagementLauncherTests.swift */, + ); + path = EngagementLauncher; + sourceTree = ""; + }; 3100D921296E943100DEC9CE /* Welcome */ = { isa = PBXGroup; children = ( @@ -3472,6 +3484,7 @@ 7512A57827BF9FB800319DF1 /* Sources */ = { isa = PBXGroup; children = ( + 215A25962CABC7C50013023E /* EngagementLauncher */, C0D6C9FE2C106A0D00D4709B /* AlertManager */, 8418AD792BFB68C9007DE207 /* OnHoldOverlayVisualEffectView */, 8485704D2BEE39EB00CEBCC5 /* ChatView */, @@ -4089,6 +4102,7 @@ 7512A5A627C3926500319DF1 /* GliaTests.swift */, 846A5C4429F6BEFA0049B29F /* GliaTests+StartEngagement.swift */, AFFA99812C57D658004A2825 /* GliaTests+RestoreEngagement.swift */, + 215A25992CAC19780013023E /* GliaTests+EngagementLauncher.swift */, ); path = Glia; sourceTree = ""; @@ -6350,6 +6364,7 @@ C03A8049292BC8DB00DDECA6 /* CallViewControllerTests.swift in Sources */, 84D5B9662A15204400807F92 /* QuickLookBased.Failing.swift in Sources */, 84602A772AEA5BEA0031E606 /* ProximityManager.Failing.swift in Sources */, + 215A259A2CAC19780013023E /* GliaTests+EngagementLauncher.swift in Sources */, 31758EC62B5FC182007BBD9F /* SceneProvider.Mock.swift in Sources */, 753B05F82AFC1D750084611E /* SerialQueueTests.swift in Sources */, 31B278032B55BE670021DEC1 /* SecureConversations.WelcomeViewController.Mock.swift in Sources */, @@ -6373,6 +6388,7 @@ AF1C19802B14FE9F00F8810F /* ConditionalCompilationClient.Failing.swift in Sources */, 84520BED2B19FD3000F97617 /* CallVisualizerTests+LO.swift in Sources */, 9A19927027D3BCAE00161AAE /* GCD.Failing.swift in Sources */, + 215A25982CABC7DF0013023E /* EngagementLauncherTests.swift in Sources */, 3142696A29FFB712003DF62E /* Interactor.Failing.swift in Sources */, 8485704F2BEE3A0800CEBCC5 /* ChatViewTest.swift in Sources */, 8491AF602AA1EBB600CC3E72 /* TranscriptModelTests+URLs.swift in Sources */, diff --git a/GliaWidgets/Public/Glia/Glia+EngagementLauncher.swift b/GliaWidgets/Public/Glia/Glia+EngagementLauncher.swift index b86076f6b..4c792ad19 100644 --- a/GliaWidgets/Public/Glia/Glia+EngagementLauncher.swift +++ b/GliaWidgets/Public/Glia/Glia+EngagementLauncher.swift @@ -8,7 +8,18 @@ extension Glia { /// /// - Returns: /// - `EngagementLauncher` instance. - public func getEngagementLauncher(queueIds: [String]?) -> EngagementLauncher { - .init(queueIds: queueIds) + public func getEngagementLauncher(queueIds: [String]?) throws -> EngagementLauncher { + let parameters = try getEngagementParameters(in: queueIds ?? []) + return try EngagementLauncher { [weak self] engagementKind, sceneProvider in + try self?.resolveEngangementState( + engagementKind: engagementKind, + sceneProvider: sceneProvider, + configuration: parameters.configuration, + interactor: parameters.interactor, + features: parameters.features, + viewFactory: parameters.viewFactory, + ongoingEngagementMediaStreams: parameters.ongoingEngagementMediaStreams + ) + } } } diff --git a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift index af12fd37f..48c7b28a1 100644 --- a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift +++ b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift @@ -24,40 +24,40 @@ extension Glia { in queueIds: [String] = [], sceneProvider: SceneProvider? = nil ) throws { + let parameters = try getEngagementParameters(in: queueIds) + + try resolveEngangementState( + engagementKind: engagementKind, + sceneProvider: sceneProvider, + configuration: parameters.configuration, + interactor: parameters.interactor, + features: parameters.features, + viewFactory: parameters.viewFactory, + ongoingEngagementMediaStreams: parameters.ongoingEngagementMediaStreams + ) + } + + /// Set up and returns parameters needed to start or restore engagement + func getEngagementParameters(in queueIds: [String] = []) throws -> EngagementParameters { // In order to align behaviour between platforms, // `GliaError.engagementExists` is no longer thrown, // instead engagement is getting restored. - guard let configuration = self.configuration else { throw GliaError.sdkIsNotConfigured } + guard let configuration else { + throw GliaError.sdkIsNotConfigured + } + + guard let interactor else { + loggerPhase.logger.prefixed(Self.self).warning("Interactor is missing") + throw GliaError.sdkIsNotConfigured + } // Interactor is initialized during configuration, which means that queueIds need // to be set in interactor when startEngagement is called. - self.interactor?.setQueuesIds(queueIds) + interactor.setQueuesIds(queueIds) + // It is assumed that `features` to be provided from `configure` or via deprecated `startEngagement` method. let features = self.features ?? [] - if let engagement = environment.coreSdk.getCurrentEngagement() { - if engagement.source == .callVisualizer { - throw GliaError.callVisualizerEngagementExists - } else { - guard let interactor else { - loggerPhase.logger.prefixed(Self.self).warning("Interactor is missing") - return - } - if let rootCoordinator { - rootCoordinator.maximize() - } else { - self.restoreOngoingEngagement( - configuration: configuration, - currentEngagement: engagement, - interactor: interactor, - features: features, - maximize: true - ) - } - return - } - } - // Apply company name to theme and get the modified theme let modifiedTheme = applyCompanyName(using: configuration, theme: theme) @@ -78,10 +78,44 @@ extension Glia { ongoingEngagementMediaStreams = .init(audio: media.audio, video: nil) } - guard let interactor else { - loggerPhase.logger.prefixed(Self.self).warning("Interactor is missing") - return + return EngagementParameters( + viewFactory: viewFactory, + interactor: interactor, + ongoingEngagementMediaStreams: ongoingEngagementMediaStreams, + features: features, + configuration: configuration + ) + } + + func resolveEngangementState( + engagementKind: EngagementKind, + sceneProvider: SceneProvider?, + configuration: Configuration, + interactor: Interactor, + features: Features, + viewFactory: ViewFactory, + ongoingEngagementMediaStreams: Engagement.Media? + ) throws { + if let engagement = environment.coreSdk.getCurrentEngagement() { + if engagement.source == .callVisualizer { + throw GliaError.callVisualizerEngagementExists + } else { + if let rootCoordinator { + rootCoordinator.maximize() + } else { + self.restoreOngoingEngagement( + configuration: configuration, + currentEngagement: engagement, + interactor: interactor, + features: features, + maximize: true + ) + } + loggerPhase.logger.prefixed(Self.self).info("Engagement was restored") + return + } } + startRootCoordinator( with: interactor, viewFactory: viewFactory, @@ -175,4 +209,13 @@ extension Glia { onEvent?(.maximized) } } + + /// The `EngagementParameters` encapsulates all parameters required to initiate or restore the coordinator + struct EngagementParameters { + let viewFactory: ViewFactory + let interactor: Interactor + let ongoingEngagementMediaStreams: Engagement.Media? + let features: Features + let configuration: Configuration + } } diff --git a/GliaWidgets/Sources/EngagementLauncher/EngagementLauncher.swift b/GliaWidgets/Sources/EngagementLauncher/EngagementLauncher.swift index 27a0e16f0..1f6b70166 100644 --- a/GliaWidgets/Sources/EngagementLauncher/EngagementLauncher.swift +++ b/GliaWidgets/Sources/EngagementLauncher/EngagementLauncher.swift @@ -4,10 +4,12 @@ import GliaCoreSDK /// `EngagementLauncher`class allows launching different types of engagements, such as chat, /// audio calls, video calls, and secure messaging. public final class EngagementLauncher { - private let queueIds: [String]? + typealias StartEngagementAction = (EngagementKind, SceneProvider?) throws -> Void - public init(queueIds: [String]?) { - self.queueIds = queueIds + private var startEngagement: StartEngagementAction + + init(startEngagement: @escaping StartEngagementAction) rethrows { + self.startEngagement = startEngagement } /// Starts a chat. @@ -15,8 +17,8 @@ public final class EngagementLauncher { /// - Parameters: /// - sceneProvider: Used to provide `UIWindowScene` to the framework. Defaults to /// the first active foreground scene. - public func startChat(sceneProvider: SceneProvider? = nil) { - startEngagement(of: .chat, sceneProvider: sceneProvider) + public func startChat(sceneProvider: SceneProvider? = nil) throws { + try startEngagement(.chat, sceneProvider) } /// Starts a audio call. @@ -24,8 +26,8 @@ public final class EngagementLauncher { /// - Parameters: /// - sceneProvider: Used to provide `UIWindowScene` to the framework. Defaults to /// the first active foreground scene. - public func startAudioCall(sceneProvider: SceneProvider? = nil) { - startEngagement(of: .audioCall, sceneProvider: sceneProvider) + public func startAudioCall(sceneProvider: SceneProvider? = nil) throws { + try startEngagement(.audioCall, sceneProvider) } /// Starts a video call. @@ -33,8 +35,8 @@ public final class EngagementLauncher { /// - Parameters: /// - sceneProvider: Used to provide `UIWindowScene` to the framework. Defaults to /// the first active foreground scene. - public func startVideoCall(sceneProvider: SceneProvider? = nil) { - startEngagement(of: .videoCall, sceneProvider: sceneProvider) + public func startVideoCall(sceneProvider: SceneProvider? = nil) throws { + try startEngagement(.videoCall, sceneProvider) } /// Starts a secure messaging. @@ -42,12 +44,7 @@ public final class EngagementLauncher { /// - Parameters: /// - sceneProvider: Used to provide `UIWindowScene` to the framework. Defaults to /// the first active foreground scene. - public func startSecureMessaging(sceneProvider: SceneProvider? = nil) { - startEngagement(of: .messaging(.welcome), sceneProvider: sceneProvider) + public func startSecureMessaging(sceneProvider: SceneProvider? = nil) throws { + try startEngagement(.messaging(.welcome), sceneProvider) } - - private func startEngagement( - of engagementKind: EngagementKind, - sceneProvider: SceneProvider? - ) {} } diff --git a/GliaWidgetsTests/Sources/EngagementLauncher/EngagementLauncherTests.swift b/GliaWidgetsTests/Sources/EngagementLauncher/EngagementLauncherTests.swift new file mode 100644 index 000000000..6df17769f --- /dev/null +++ b/GliaWidgetsTests/Sources/EngagementLauncher/EngagementLauncherTests.swift @@ -0,0 +1,50 @@ +import XCTest + +@testable import GliaWidgets + +final class EngagementLauncherTests: XCTestCase { + + func test_startChat() throws { + var chatEngagement: EngagementKind? + var engagementLauncher = EngagementLauncher { engagementKind, _ in + chatEngagement = engagementKind + } + + try engagementLauncher.startChat() + + XCTAssertEqual(chatEngagement, .chat) + } + + func test_startAudioCall() throws { + var chatEngagement: EngagementKind? + var engagementLauncher = EngagementLauncher { engagementKind, _ in + chatEngagement = engagementKind + } + + try engagementLauncher.startAudioCall() + + XCTAssertEqual(chatEngagement, .audioCall) + } + + func test_startVideoCall() throws { + var chatEngagement: EngagementKind? + var engagementLauncher = EngagementLauncher { engagementKind, _ in + chatEngagement = engagementKind + } + + try engagementLauncher.startVideoCall() + + XCTAssertEqual(chatEngagement, .videoCall) + } + + func test_startSecureMessaging() throws { + var chatEngagement: EngagementKind? + var engagementLauncher = EngagementLauncher { engagementKind, _ in + chatEngagement = engagementKind + } + + try engagementLauncher.startSecureMessaging() + + XCTAssertEqual(chatEngagement, .messaging(.welcome)) + } +} diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests+EngagementLauncher.swift b/GliaWidgetsTests/Sources/Glia/GliaTests+EngagementLauncher.swift new file mode 100644 index 000000000..6f348d19e --- /dev/null +++ b/GliaWidgetsTests/Sources/Glia/GliaTests+EngagementLauncher.swift @@ -0,0 +1,106 @@ +@testable import GliaWidgets +import XCTest + +extension GliaTests { + func test_getEngagementLauncherDoesNotThrowErrorWithCorrectConfiguration() throws { + let sdk = makeConfigurableSDK() + + try sdk.configure( + with: .mock(), + theme: .mock() + ) { _ in } + + XCTAssertNoThrow( + try sdk.getEngagementLauncher(queueIds: nil) + ) + } + + func test_startChatUsingEngagementLauncherWithCorrectConfiguration() throws { + let sdk = makeConfigurableSDK() + + try sdk.configure( + with: .mock(), + theme: .mock() + ) { _ in } + + let engagementLauncher = try sdk.getEngagementLauncher(queueIds: nil) + + try engagementLauncher.startChat() + + XCTAssertEqual(sdk.engagement, .chat) + } + + func test_startAudioCallUsingEngagementLauncherWithCorrectConfiguration() throws { + let sdk = makeConfigurableSDK() + + try sdk.configure( + with: .mock(), + theme: .mock() + ) { _ in } + + let engagementLauncher = try sdk.getEngagementLauncher(queueIds: nil) + + try engagementLauncher.startAudioCall() + + XCTAssertEqual(sdk.engagement, .audioCall) + } + + func test_startVideoCallUsingEngagementLauncherWithCorrectConfiguration() throws { + let sdk = makeConfigurableSDK() + + try sdk.configure( + with: .mock(), + theme: .mock() + ) { _ in } + + let engagementLauncher = try sdk.getEngagementLauncher(queueIds: nil) + + try engagementLauncher.startVideoCall() + + XCTAssertEqual(sdk.engagement, .videoCall) + } + + func test_startSecureConversationUsingEngagementLauncherWithCorrectConfiguration() throws { + let sdk = makeConfigurableSDK() + + try sdk.configure( + with: .mock(), + theme: .mock() + ) { _ in } + + let engagementLauncher = try sdk.getEngagementLauncher(queueIds: nil) + + try engagementLauncher.startSecureMessaging() + + XCTAssertEqual(sdk.engagement, .messaging(.welcome)) + } +} + +private extension GliaTests { + func makeConfigurableSDK() -> Glia { + var sdkEnv = Glia.Environment.failing + sdkEnv.coreSDKConfigurator.configureWithInteractor = { _ in } + sdkEnv.coreSdk.localeProvider = .mock + sdkEnv.createRootCoordinator = { _, _, _, engagementKind, _, _, _ in + .mock(engagementKind: engagementKind, environment: .engagementCoordEnvironmentWithKeyWindow) + } + sdkEnv.print.printClosure = { _, _, _ in } + var logger = CoreSdkClient.Logger.failing + logger.configureLocalLogLevelClosure = { _ in } + logger.configureRemoteLogLevelClosure = { _ in } + logger.prefixedClosure = { _ in logger } + logger.infoClosure = { _, _, _, _ in } + sdkEnv.coreSdk.createLogger = { _ in logger } + sdkEnv.conditionalCompilation.isDebug = { true } + sdkEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion(.success(())) + } + sdkEnv.coreSdk.getCurrentEngagement = { nil } + let window = UIWindow(frame: .zero) + window.makeKeyAndVisible() + sdkEnv.uiApplication.windows = { [window] } + let sdk = Glia(environment: sdkEnv) + + return sdk + } +} diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift index 89e54e5e7..f7bf3335e 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift @@ -95,6 +95,7 @@ extension GliaTests { var gliaEnv = Glia.Environment.failing gliaEnv.conditionalCompilation.isDebug = { false } gliaEnv.coreSdk.createLogger = { _ in logger } + gliaEnv.coreSdk.localeProvider.getRemoteString = { _ in nil } let sdk = Glia(environment: gliaEnv) sdk.environment.conditionalCompilation.isDebug = { true } sdk.environment.coreSDKConfigurator.configureWithInteractor = { _ in }