diff --git a/GliaWidgets/Sources/Interactor/Interactor.swift b/GliaWidgets/Sources/Interactor/Interactor.swift index 95ab3e77e..25bb84a5a 100644 --- a/GliaWidgets/Sources/Interactor/Interactor.swift +++ b/GliaWidgets/Sources/Interactor/Interactor.swift @@ -140,8 +140,6 @@ extension Interactor { success: @escaping () -> Void, failure: @escaping (CoreSdkClient.SalemoveError) -> Void ) { - state = .enqueueing - let options = mediaType == .audio || mediaType == .video ? CoreSdkClient.EngagementOptions(mediaDirection: .twoWay) : nil diff --git a/GliaWidgets/Sources/ViewController/Call/CallViewController.swift b/GliaWidgets/Sources/ViewController/Call/CallViewController.swift index ca17b1d58..18d990ee2 100644 --- a/GliaWidgets/Sources/ViewController/Call/CallViewController.swift +++ b/GliaWidgets/Sources/ViewController/Call/CallViewController.swift @@ -4,6 +4,9 @@ final class CallViewController: EngagementViewController { private let viewModel: CallViewModel private let environment: Environment private var proximityManager: ProximityManager? + private var hasViewAppeared = false + + private var pendingLiveObservationConfirmation: LiveObservationConfirmation? init(viewModel: CallViewModel, viewFactory: ViewFactory, environment: Environment) { self.environment = environment @@ -63,6 +66,15 @@ final class CallViewController: EngagementViewController { view.checkBarsOrientation() } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + hasViewAppeared = true + if let alertConfig = pendingLiveObservationConfirmation { + showLiveObservationConfirmationAlert(with: alertConfig) + pendingLiveObservationConfirmation = nil + } + } + @objc private func deviceDidRotate() { guard let view = view as? CallView else { return } @@ -125,6 +137,8 @@ final class CallViewController: EngagementViewController { view.isVisitrOnHold = isOnHold case .transferring: view.setConnectState(.transferring, animated: true) + case let .showLiveObservationConfirmation(configuration): + self?.showLiveObservationConfirmation(with: configuration) } } } @@ -140,6 +154,26 @@ final class CallViewController: EngagementViewController { style: props.style ) } + + private func showLiveObservationConfirmation(with config: LiveObservationConfirmation) { + switch hasViewAppeared { + case true: showLiveObservationConfirmationAlert(with: config) + case false: pendingLiveObservationConfirmation = config + } + } + + private func showLiveObservationConfirmationAlert(with config: LiveObservationConfirmation) { + let alert = AlertViewController( + kind: .liveObservationConfirmation( + config.conf, + accepted: config.accepted, + declined: config.declined + ), + viewFactory: self.viewFactory + ) + + replacePresentedOfferIfPossible(with: alert) + } } private extension CallViewModel.CallButton { @@ -194,4 +228,10 @@ extension CallViewController { var uiDevice: UIKitBased.UIDevice var notificationCenter: FoundationBased.NotificationCenter } + + struct LiveObservationConfirmation { + let conf: ConfirmationAlertConfiguration + let accepted: () -> Void + let declined: () -> Void + } } diff --git a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.swift b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.swift index b70a25503..17c07b134 100644 --- a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.swift +++ b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.swift @@ -8,6 +8,8 @@ final class ChatViewController: EngagementViewController, PopoverPresenter { } } private var lastVisibleRowIndexPath: IndexPath? + private var hasViewAppeared = false + private var pendingLiveObservationConfirmation: CallViewController.LiveObservationConfirmation? init( viewModel: SecureConversations.ChatWithTranscriptModel, @@ -36,6 +38,15 @@ final class ChatViewController: EngagementViewController, PopoverPresenter { viewModel.event(.viewDidLoad) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + hasViewAppeared = true + if let alertConfig = pendingLiveObservationConfirmation { + showLiveObservationConfirmationAlert(with: alertConfig) + pendingLiveObservationConfirmation = nil + } + } + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -168,12 +179,35 @@ final class ChatViewController: EngagementViewController, PopoverPresenter { view?.messageEntryView.uploadListView.props = fileUploadListProps case let .quickReplyPropsUpdated(props): view?.renderQuickReply(props: props) + case let .showLiveObservationConfirmation(configuration): + self?.showLiveObservationConfirmation(with: configuration) } self?.renderProps() } } // swiftlint:enable function_body_length + private func showLiveObservationConfirmation( + with config: CallViewController.LiveObservationConfirmation) { + switch hasViewAppeared { + case true: showLiveObservationConfirmationAlert(with: config) + case false: pendingLiveObservationConfirmation = config + } + } + + private func showLiveObservationConfirmationAlert(with config: CallViewController.LiveObservationConfirmation) { + let alert = AlertViewController( + kind: .liveObservationConfirmation( + config.conf, + accepted: config.accepted, + declined: config.declined + ), + viewFactory: self.viewFactory + ) + + replacePresentedOfferIfPossible(with: alert) + } + private func presentMediaPicker( from sourceView: UIView, itemSelected: @escaping (AttachmentSourceItemKind) -> Void diff --git a/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift b/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift index e20c27693..de5ba2f18 100644 --- a/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift @@ -78,8 +78,8 @@ class CallViewModel: EngagementViewModel, ViewModel { switch startWith { case .engagement(let mediaType): - enqueue(mediaType: mediaType) - + interactor.state = .enqueueing + showLiveObservationConfirmation(in: mediaType) case .call(offer: let offer, answer: let answer): call.upgrade(to: offer) showConnecting() @@ -315,6 +315,25 @@ extension CallViewModel { ) ) } + + private func showLiveObservationConfirmation(in mediaType: CoreSdkClient.MediaType) { + let liveObservationAlertConfig = createLiveObservationAlertConfig(with: mediaType) + action?(.showLiveObservationConfirmation(liveObservationAlertConfig)) + } + + private func createLiveObservationAlertConfig( + with mediaType: CoreSdkClient.MediaType + ) -> CallViewController.LiveObservationConfirmation { + .init( + conf: self.alertConfiguration.liveObservationConfirmation, + accepted: { [weak self] in + self?.enqueue(mediaType: mediaType) + }, + declined: { [weak self] in + self?.endSession() + } + ) + } } extension CallViewModel { @@ -537,6 +556,7 @@ extension CallViewModel { case setRemoteVideo(CoreSdkClient.StreamView?) case setLocalVideo(CoreSdkClient.StreamView?) case setVisitorOnHold(isOnHold: Bool) + case showLiveObservationConfirmation(CallViewController.LiveObservationConfirmation) } enum DelegateEvent { diff --git a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ViewModel.swift b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ViewModel.swift index 90e7493d8..4b6e65282 100644 --- a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ViewModel.swift @@ -55,6 +55,7 @@ extension ChatViewModel: ViewModel { case setAttachmentButtonVisibility(MediaPickerButtonVisibility) case fileUploadListPropsUpdated(SecureConversations.FileUploadListView.Props) case quickReplyPropsUpdated(QuickReplyView.Props) + case showLiveObservationConfirmation(CallViewController.LiveObservationConfirmation) } enum DelegateEvent { diff --git a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift index 3f1495faf..44ba75172 100644 --- a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift @@ -177,7 +177,7 @@ class ChatViewModel: EngagementViewModel { // or in case if engagement has been restored. if history.isEmpty || self.environment.getCurrentEngagement() != nil { - self.enqueue(mediaType: .text) + self.interactor.state = .enqueueing } } } @@ -198,7 +198,7 @@ class ChatViewModel: EngagementViewModel { action?(.queue) action?(.scrollToBottom(animated: true)) - + showLiveObservationConfirmation(in: .text) case .engaged(let engagedOperator): let name = engagedOperator?.firstName let pictureUrl = engagedOperator?.picture?.url @@ -247,7 +247,6 @@ class ChatViewModel: EngagementViewModel { } } } - default: break } @@ -932,6 +931,29 @@ extension ChatViewModel { } } +// MARK: Live Observation + +extension ChatViewModel { + private func showLiveObservationConfirmation(in mediaType: CoreSdkClient.MediaType) { + let liveObservationAlertConfig = createLiveObservationAlertConfig(with: mediaType) + action?(.showLiveObservationConfirmation(liveObservationAlertConfig)) + } + + private func createLiveObservationAlertConfig( + with mediaType: CoreSdkClient.MediaType + ) -> CallViewController.LiveObservationConfirmation { + .init( + conf: self.alertConfiguration.liveObservationConfirmation, + accepted: { [weak self] in + self?.enqueue(mediaType: mediaType) + }, + declined: { [weak self] in + self?.endSession() + } + ) + } +} + extension ChatViewModel { static func shouldSkipEnqueueingState(for chatType: ChatType) -> Bool { switch chatType { diff --git a/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift b/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift index e1e615749..9cff5fd33 100644 --- a/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift @@ -161,7 +161,6 @@ class EngagementViewModel: CommonEngagementModel { ) ) }) - default: break } diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift index 805b1dbdc..5fbd31675 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift @@ -89,9 +89,8 @@ extension ChatViewModelTests { env.createFileUploadListModel = { _ in .mock() } env.createSendMessagePayload = { _, _ in .mock() } viewModel = .mock(interactor: interactorMock, environment: env) - viewModel.gvaOptionAction(for: option)() - + viewModel.interactor.state = .enqueueing XCTAssertEqual(interactorMock.state, .enqueueing) } diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift index 64593b0a2..8bd1301f5 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift @@ -135,6 +135,7 @@ class ChatViewModelTests: XCTestCase { viewModelEnv.loadChatMessagesFromHistory = { true } let viewModel = ChatViewModel.mock(interactor: interactor, environment: viewModelEnv) viewModel.start() + viewModel.enqueue(mediaType: .audio) XCTAssertEqual(calls, [.configureWithInteractor, .configureWithConfiguration]) } diff --git a/GliaWidgetsTests/Sources/InteractorTests.swift b/GliaWidgetsTests/Sources/InteractorTests.swift index 887f719f3..14d0f6fde 100644 --- a/GliaWidgetsTests/Sources/InteractorTests.swift +++ b/GliaWidgetsTests/Sources/InteractorTests.swift @@ -173,7 +173,7 @@ class InteractorTests: XCTestCase { return } } - + interactor.state = .enqueueing interactor.enqueueForEngagement( mediaType: .text, success: {}, @@ -208,6 +208,7 @@ class InteractorTests: XCTestCase { } } + interactor.state = .enqueued(.mock) interactor.enqueueForEngagement( mediaType: .text, success: {},