From 417bc577d7af0a48acfd3f55facc61c291f32e9e Mon Sep 17 00:00:00 2001 From: Egor Egorov Date: Wed, 11 Oct 2023 15:16:29 +0300 Subject: [PATCH] Connecting state issue fixes On SDK configuration, interactor instance is created and has .none state. When Call screen is loaded, its start() method is called which executes some updates based on current interactor.state. When configure method is called before each engagement start (like we currently do in WidgetsSDK Testing app), CallViewModel.start() does nothing, because interactor.state is .none. But when WidgesSDK is configured once (for example on the app launch), the same interactor instance is used for all subsequent engagements. When you end first engagement (Chat/Audio/Video, probably CV also), interactor.state is .ended. Then if you start new Audio/Video engagement, CallViewModel.start() calls CallViewModel.call.end() method, which breaks something and connecting state becomes infinite. This commit fixes calling CallViewModel.call.end() on Audio/Video engagement start. Commit also make two engagement references weak to avoid retaining it when CoreSDK already released it from memory. The toggle (UISwitch) was added to Main screen, that provides the ability to disable/enable SDK configuration before each engagement. MOB-2730 --- .../ViewModel/Call/CallViewModel.swift | 15 +++++- .../ViewModel/EngagementViewModel.swift | 10 ++-- .../Sources/CallViewModelTests.swift | 12 +++++ TestingApp/Main.storyboard | 50 ++++++++++++------- .../ViewController/ViewController.swift | 47 ++++++++++++----- 5 files changed, 99 insertions(+), 35 deletions(-) diff --git a/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift b/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift index ec2f08e4d..86a705cb5 100644 --- a/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift @@ -72,7 +72,20 @@ class CallViewModel: EngagementViewModel, ViewModel { super.start() update(for: call.kind.value) - update(for: interactor.state) + + // In the case when SDK is configured once and then + // visitor has several Audio/Video engagements in a raw, + // after ending each of them, `interactor.state` has `.ended` value, + // which causes calling `call.end()` on the start of the next + // Audio/Video engagement. That `call.end()` breaks the flow and SDK does not + // handle `connected` state properly. So we need to skip handling `.ended` state + // on the start of a new engagement. + switch interactor.state { + case .ended: + break + default: + update(for: interactor.state) + } switch startWith { case .engagement(let mediaType): diff --git a/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift b/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift index e1e615749..26e843a84 100644 --- a/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift @@ -11,6 +11,8 @@ class EngagementViewModel: CommonEngagementModel { let alertConfiguration: AlertConfiguration let environment: Environment let screenShareHandler: ScreenShareHandler + // Need to keep strong reference of `activeEngagement`, + // to be able to fetch survey after ending engagement var activeEngagement: CoreSdkClient.Engagement? private(set) var isViewActive = ObservableValue(with: false) @@ -225,10 +227,10 @@ class EngagementViewModel: CommonEngagementModel { } func endSession() { - interactor.endSession { - self.engagementDelegate?(.finished) - } failure: { _ in - self.engagementDelegate?(.finished) + interactor.endSession { [weak self] in + self?.engagementDelegate?(.finished) + } failure: { [weak self] _ in + self?.engagementDelegate?(.finished) } self.screenShareHandler.stop(nil) } diff --git a/GliaWidgetsTests/Sources/CallViewModelTests.swift b/GliaWidgetsTests/Sources/CallViewModelTests.swift index b3500afff..68b97d7e4 100644 --- a/GliaWidgetsTests/Sources/CallViewModelTests.swift +++ b/GliaWidgetsTests/Sources/CallViewModelTests.swift @@ -335,4 +335,16 @@ class CallViewModelTests: XCTestCase { XCTAssertEqual(call.kind.value, .audio) } + + func test_startMethodDoesNotHandleInteractorStateEnded() { + let interactor: Interactor = .mock() + interactor.state = .ended(.byOperator) + let call: Call = .mock() + let viewModel: CallViewModel = .mock(interactor: interactor, call: call) + + XCTAssertEqual(call.state.value, .none) + viewModel.start() + + XCTAssertEqual(call.state.value, .none) + } } diff --git a/TestingApp/Main.storyboard b/TestingApp/Main.storyboard index f573b303d..dea23e613 100644 --- a/TestingApp/Main.storyboard +++ b/TestingApp/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -21,7 +21,7 @@ - + + + + + + + + + + + - + - +