From f599f951369cc59115d74fd3645c685dec797396 Mon Sep 17 00:00:00 2001 From: Yevhen Kyivskyi Date: Sun, 13 Oct 2024 16:34:54 +0200 Subject: [PATCH] Integrate queues monitor and engagement launcher into EntryWidget --- GliaWidgets.xcodeproj/project.pbxproj | 26 ++++- .../Public/Glia/Glia+EntryWidget.swift | 11 ++- .../EntryWidget/EntryWidget.Channel.swift | 21 ++++ .../EntryWidget/EntryWidget.Environment.swift | 9 ++ .../Sources/EntryWidget/EntryWidget.swift | 97 +++++++++++++------ .../EntryWidget/EntryWidgetViewModel.swift | 20 ++-- .../Glia.Environment.Interface.swift | 1 + .../Glia.Environment.Live.swift | 9 +- .../Glia.Environment.Mock.swift | 3 +- .../QueuesMonitor.Environment.swift | 4 +- ...Monitor.swift => QueuesMonitor.Live.swift} | 35 ++++--- .../QueuesMonitor/QueuesMonitor.Mock.swift | 13 +++ GliaWidgets/Sources/Utilities/CancelBag.swift | 3 + .../Glia.Environment.Failing.swift | 3 +- GliaWidgetsTests/QueuesMonitor.Failing.swift | 11 +++ Podfile.lock | 3 +- .../ViewController/ViewController.swift | 11 ++- 17 files changed, 214 insertions(+), 66 deletions(-) create mode 100644 GliaWidgets/Sources/EntryWidget/EntryWidget.Environment.swift rename GliaWidgets/Sources/QueuesMonitor/{QueuesMonitor.swift => QueuesMonitor.Live.swift} (64%) create mode 100644 GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Mock.swift create mode 100644 GliaWidgets/Sources/Utilities/CancelBag.swift create mode 100644 GliaWidgetsTests/QueuesMonitor.Failing.swift diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index f782daca5..29ddbaf51 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -166,11 +166,16 @@ 1AFB1E7425F8B00B00CA460D /* ChatTextContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AFB1E7325F8B00B00CA460D /* ChatTextContentView.swift */; }; 1AFB1E7825F8B26800CA460D /* ChatTextContentStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AFB1E7725F8B26800CA460D /* ChatTextContentStyle.swift */; }; 2100B4802CB6B5A400AC7527 /* LockIsolated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */; }; + 2100B47C2CB66B6500AC7527 /* EntryWidget.Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47B2CB66B6500AC7527 /* EntryWidget.Environment.swift */; }; + 2100B47E2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47D2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift */; }; + 2100B4802CB6B5A400AC7527 /* LockIsolated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */; }; + 2100B4842CB8143400AC7527 /* QueuesMonitor.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B4832CB8143400AC7527 /* QueuesMonitor.Failing.swift */; }; + 2100B4872CB91E7B00AC7527 /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B4862CB91E7B00AC7527 /* CancelBag.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 */; }; - 2198B7AC2CAEB14D002C442B /* QueuesMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2198B7AB2CAEB14D002C442B /* QueuesMonitor.swift */; }; + 2198B7AC2CAEB14D002C442B /* QueuesMonitor.Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2198B7AB2CAEB14D002C442B /* QueuesMonitor.Live.swift */; }; 2198B7AE2CB035A6002C442B /* QueuesMonitor.Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2198B7AD2CB035A6002C442B /* QueuesMonitor.Environment.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 */; }; @@ -1210,11 +1215,16 @@ 1AFB1E7325F8B00B00CA460D /* ChatTextContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextContentView.swift; sourceTree = ""; }; 1AFB1E7725F8B26800CA460D /* ChatTextContentStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextContentStyle.swift; sourceTree = ""; }; 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockIsolated.swift; sourceTree = ""; }; + 2100B47B2CB66B6500AC7527 /* EntryWidget.Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryWidget.Environment.swift; sourceTree = ""; }; + 2100B47D2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitor.Mock.swift; sourceTree = ""; }; + 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockIsolated.swift; sourceTree = ""; }; + 2100B4832CB8143400AC7527 /* QueuesMonitor.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitor.Failing.swift; sourceTree = ""; }; + 2100B4862CB91E7B00AC7527 /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.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 = ""; }; - 2198B7AB2CAEB14D002C442B /* QueuesMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitor.swift; sourceTree = ""; }; + 2198B7AB2CAEB14D002C442B /* QueuesMonitor.Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitor.Live.swift; sourceTree = ""; }; 2198B7AD2CB035A6002C442B /* QueuesMonitor.Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitor.Environment.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 = ""; }; @@ -2349,6 +2359,7 @@ 9A3E1D9C27BA7741005634EB /* FoundationBased.Failing.swift */, 84D5B9652A15204400807F92 /* QuickLookBased.Failing.swift */, 9A1992E627D66C7400161AAE /* UIKitBased.Failing.swift */, + 2100B4832CB8143400AC7527 /* QueuesMonitor.Failing.swift */, AF6291142B0818DE00D3D76B /* SwiftBased.Failing.swift */, AFF9542B2ADDA10600C277E0 /* CoreSDKConfigurator.Failing.swift */, AF1C197F2B14FE9F00F8810F /* ConditionalCompilationClient.Failing.swift */, @@ -3226,6 +3237,7 @@ isa = PBXGroup; children = ( 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */, + 2100B4862CB91E7B00AC7527 /* CancelBag.swift */, ); path = Utilities; sourceTree = ""; @@ -3249,7 +3261,8 @@ 2198B7AA2CAEB13E002C442B /* QueuesMonitor */ = { isa = PBXGroup; children = ( - 2198B7AB2CAEB14D002C442B /* QueuesMonitor.swift */, + 2198B7AB2CAEB14D002C442B /* QueuesMonitor.Live.swift */, + 2100B47D2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift */, 2198B7AD2CB035A6002C442B /* QueuesMonitor.Environment.swift */, ); path = QueuesMonitor; @@ -5038,6 +5051,7 @@ children = ( C0F7EA372CA1D6D40038019C /* CustomPresentationController.swift */, C0F3DE362C69F51D00DE6D7B /* EntryWidget.swift */, + 2100B47B2CB66B6500AC7527 /* EntryWidget.Environment.swift */, C0F3DE3E2C6E176A00DE6D7B /* EntryWidget.Channel.swift */, C0F3DE382C69FC2100DE6D7B /* EntryWidget.Presentation.swift */, C0F7EA392CA1D7050038019C /* EntryWidget.SizeConstraints.swift */, @@ -5638,7 +5652,7 @@ 1A6EBB0325ADB82000EE325D /* MediaUpgradeActionView.swift in Sources */, 9AB196E627C510DA00FD60AB /* ChatMessage.Mock.swift in Sources */, C08D776428F5910A000461E5 /* UIView+Extensions.swift in Sources */, - 2198B7AC2CAEB14D002C442B /* QueuesMonitor.swift in Sources */, + 2198B7AC2CAEB14D002C442B /* QueuesMonitor.Live.swift in Sources */, C090467F2B7D022C003C437C /* WelcomeStyle.FilePickerButtonStyle.RemoteConfig.swift in Sources */, C0D6CA4F2C19B9A300D4709B /* GliaPresenter.Environment.swift in Sources */, 9A19926A27D3BA8700161AAE /* ViewFactory.Environment.Interface.swift in Sources */, @@ -5767,6 +5781,7 @@ C0F3DE3B2C6E0DD900DE6D7B /* EntryWidgetView.swift in Sources */, C090474C2B7E210C003C437C /* FileUploadStyle.Equatable.swift in Sources */, C09047402B7E1FBC003C437C /* MessageCenterFileUploadStyle.swift in Sources */, + 2100B47E2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift in Sources */, 9A8130BB27D7A41000220BBD /* FileUpload.Environment.Interface.swift in Sources */, C09046AB2B7D0967003C437C /* WelcomeStyle.SendButton.DisabledStyle.Accessibility.swift in Sources */, 84520BE72B1769AB00F97617 /* ChatViewController.Environment.swift in Sources */, @@ -5811,6 +5826,7 @@ C06A7588296ECD75006B69A2 /* Theme+VisitorCode.swift in Sources */, C090476C2B7E24A8003C437C /* ChoiceCardOptionStateStyle.RemoteConfig.swift in Sources */, C0857DED28D4831E008D171D /* Theme.Text.swift in Sources */, + 2100B47C2CB66B6500AC7527 /* EntryWidget.Environment.swift in Sources */, 848B8ADC2C0759C500E990E6 /* Theme.CustomCardContainerStyle.swift in Sources */, 845876A22823FF34007AC3DF /* Survey.ViewController.Props.Mock.swift in Sources */, C090471D2B7E1AFF003C437C /* GvaGalleryCardStyle.ButtonStyle.swift in Sources */, @@ -5880,6 +5896,7 @@ 7594098C298D38C2008B173A /* CallVisualizer.Environment.swift in Sources */, 1A1E309B25F8E1F700850E68 /* DataStorage.swift in Sources */, AF6AB34D298A9F2500003645 /* SecureConversations.FileUploadListViewModel.swift in Sources */, + 2100B4872CB91E7B00AC7527 /* CancelBag.swift in Sources */, C0D6CA292C199A4700D4709B /* UnreadMessageIndicatorView.Environment.swift in Sources */, C0D6CA1B2C185BDE00D4709B /* EngagementView.Environment.swift in Sources */, C0D6CA1B2C185BDE00D4709B /* EngagementView.Environment.swift in Sources */, @@ -6505,6 +6522,7 @@ 847A7643285A1914004044D1 /* FileUploadListViewModelTests.swift in Sources */, 9A1992E727D66C7400161AAE /* UIKitBased.Failing.swift in Sources */, 31FF0DCB2B5907C600834AFB /* ChatCoordinatorTests.swift in Sources */, + 2100B4842CB8143400AC7527 /* QueuesMonitor.Failing.swift in Sources */, 3146C9432AB1851C0047D8CC /* LocalizationTests.swift in Sources */, 8492F9172CAD2F2000242691 /* TranscriptModelTests+ResponseCard.swift in Sources */, 3115EFBA2BC960B500B24D5A /* (null) in Sources */, diff --git a/GliaWidgets/Public/Glia/Glia+EntryWidget.swift b/GliaWidgets/Public/Glia/Glia+EntryWidget.swift index 808010b05..d63b57ee0 100644 --- a/GliaWidgets/Public/Glia/Glia+EntryWidget.swift +++ b/GliaWidgets/Public/Glia/Glia+EntryWidget.swift @@ -8,8 +8,13 @@ extension Glia { /// /// - Returns: /// - `EntryWidget` instance. - public func getEntryWidget(queueIds: [String]) -> EntryWidget { - // The real implementation will be added once EngagementLauncher is added - .init(theme: theme) + public func getEntryWidget(queueIds: [String]) throws -> EntryWidget { + EntryWidget( + environment: .init( + queuesMonitor: environment.queuesMonitor, + engagementLauncher: try getEngagementLauncher(queueIds: queueIds), + theme: theme + ) + ) } } diff --git a/GliaWidgets/Sources/EntryWidget/EntryWidget.Channel.swift b/GliaWidgets/Sources/EntryWidget/EntryWidget.Channel.swift index dcc60399f..8ae4ebd84 100644 --- a/GliaWidgets/Sources/EntryWidget/EntryWidget.Channel.swift +++ b/GliaWidgets/Sources/EntryWidget/EntryWidget.Channel.swift @@ -1,3 +1,4 @@ +import GliaCoreSDK import SwiftUI extension EntryWidget { @@ -47,3 +48,23 @@ extension EntryWidget { } } } + +extension EntryWidget.Channel { + init?(mediaType: MediaType) { + switch mediaType { + case .audio: + self = .audio + case .video: + self = .video + case .text: + self = .chat + case .messaging: + self = .secureMessaging + case .phone, .unknown: + return nil + @unknown default: + debugPrint("💥 Unknown type MediaType received in: \(Self.self)") + return nil + } + } +} diff --git a/GliaWidgets/Sources/EntryWidget/EntryWidget.Environment.swift b/GliaWidgets/Sources/EntryWidget/EntryWidget.Environment.swift new file mode 100644 index 000000000..1fde5c9fd --- /dev/null +++ b/GliaWidgets/Sources/EntryWidget/EntryWidget.Environment.swift @@ -0,0 +1,9 @@ +import Foundation + +extension EntryWidget { + struct Environment { + var queuesMonitor: QueuesMonitor + var engagementLauncher: EngagementLauncher + var theme: Theme + } +} diff --git a/GliaWidgets/Sources/EntryWidget/EntryWidget.swift b/GliaWidgets/Sources/EntryWidget/EntryWidget.swift index 9ba40a747..6c65d9754 100644 --- a/GliaWidgets/Sources/EntryWidget/EntryWidget.swift +++ b/GliaWidgets/Sources/EntryWidget/EntryWidget.swift @@ -1,4 +1,6 @@ +import Combine import Foundation +import GliaCoreSDK import UIKit import SwiftUI @@ -7,9 +9,7 @@ public final class EntryWidget: NSObject { private var embeddedView: UIView? private var queueIds: [String] = [] - // Channels will become dynamic in the subsequent PRs - private var channels: [Channel] = [.chat, .audio, .video, .secureMessaging] - private let theme: Theme + @Published private var channels: [Channel] = [] private let sizeConstraints: SizeConstraints = .init( singleCellHeight: 72, singleCellIconSize: 24, @@ -20,11 +20,23 @@ public final class EntryWidget: NSObject { dividerHeight: 1 ) - init(theme: Theme) { - self.theme = theme + private let environment: Environment + + private var cancellables = CancelBag() + + init(environment: Environment) { + self.environment = environment + super.init() + + environment.queuesMonitor.$state + .sink(receiveValue: handleQueuesMonitorUpdates(state:)) + .store(in: &cancellables) } public func show(by presentation: EntryWidget.Presentation) { + defer { + environment.queuesMonitor.startMonitoring(queuesIds: queueIds) + } switch presentation { case let .sheet(parentViewController): showSheet(in: parentViewController) @@ -38,14 +50,8 @@ public final class EntryWidget: NSObject { hostedViewController = nil embeddedView?.removeFromSuperview() embeddedView = nil - } - func createEntryWidgetView( - queueIds: [String], - channels: [Channel] = [.chat, .audio, .video] - ) { - self.queueIds = queueIds - self.channels = channels + environment.queuesMonitor.stopMonitoring() } } @@ -55,10 +61,7 @@ private extension EntryWidget { let model = makeViewModel( showHeader: false, channels: channels, - selection: { channel in - // Logic for handling this callback will be handled later - MOB-3473 - print(channel.headline) - } + selection: channelSelected(_:) ) let view = makeView(model: model) let hostingController = UIHostingController(rootView: view) @@ -80,21 +83,18 @@ private extension EntryWidget { let model = makeViewModel( showHeader: true, channels: channels, - selection: { channel in - // Logic for handling this callback will be handled later - MOB-3473 - print(channel.headline) - } + selection: channelSelected(_:) ) let view = makeView(model: model) let hostingController = UIHostingController(rootView: view) - switch theme.entryWidget.backgroundColor { + switch environment.theme.entryWidget.backgroundColor { case .fill(let color): hostingController.view.backgroundColor = color case .gradient(let colors): hostingController.view.makeGradientBackground( colors: colors, - cornerRadius: theme.entryWidget.cornerRadius + cornerRadius: environment.theme.entryWidget.cornerRadius ) } @@ -113,7 +113,7 @@ private extension EntryWidget { } sheet.detents = [smallDetent] sheet.prefersScrollingExpandsWhenScrolledToEdge = true - sheet.preferredCornerRadius = theme.entryWidget.cornerRadius + sheet.preferredCornerRadius = environment.theme.entryWidget.cornerRadius } else { hostingController.modalPresentationStyle = .custom hostingController.transitioningDelegate = self @@ -130,13 +130,13 @@ private extension EntryWidget { func makeViewModel( showHeader: Bool, channels: [Channel], - selection: @escaping (Channel) -> Void + selection: @escaping (Channel) throws -> Void ) -> EntryWidgetView.Model { .init( - theme: theme, + theme: environment.theme, showHeader: showHeader, sizeConstrainsts: sizeConstraints, - channels: channels, + channels: $channels, channelSelected: selection ) } @@ -172,7 +172,50 @@ extension EntryWidget: UIViewControllerTransitioningDelegate { presentedViewController: presented, presenting: presenting, height: height, - cornerRadius: theme.entryWidget.cornerRadius + cornerRadius: environment.theme.entryWidget.cornerRadius ) } } + +private extension EntryWidget { + func handleQueuesMonitorUpdates(state: QueuesMonitor.State) { + switch state { + case .idle: + break + case .updated(let queues): + let availableChannels = resolveAvailableChannels(from: queues) + self.channels = availableChannels + case .failed(let error): + // TODO: Handle error on EntryWidgetView + print(error) + } + } + + func resolveAvailableChannels(from queues: [Queue]) -> [Channel] { + var availableChannels: Set = [] + + queues.forEach { queue in + queue.state.media.forEach { mediaType in + guard let channel = Channel(mediaType: mediaType) else { + return + } + availableChannels.insert(channel) + } + } + // TODO: Add sorting for representing on UI + return Array(availableChannels) + } + + func channelSelected(_ channel: Channel) throws { + switch channel { + case .chat: + try environment.engagementLauncher.startChat() + case .audio: + try environment.engagementLauncher.startAudioCall() + case .video: + try environment.engagementLauncher.startVideoCall() + case .secureMessaging: + try environment.engagementLauncher.startSecureMessaging() + } + } +} diff --git a/GliaWidgets/Sources/EntryWidget/EntryWidgetViewModel.swift b/GliaWidgets/Sources/EntryWidget/EntryWidgetViewModel.swift index 80d2d21c2..7ee191cab 100644 --- a/GliaWidgets/Sources/EntryWidget/EntryWidgetViewModel.swift +++ b/GliaWidgets/Sources/EntryWidget/EntryWidgetViewModel.swift @@ -3,9 +3,9 @@ import SwiftUI extension EntryWidgetView { class Model: ObservableObject { @Published var viewState: ViewState + @Published var channels: [EntryWidget.Channel] = [] let theme: Theme - let channelSelected: (EntryWidget.Channel) -> Void - let channels: [EntryWidget.Channel] + let channelSelected: (EntryWidget.Channel) throws -> Void let sizeConstraints: EntryWidget.SizeConstraints let showHeader: Bool @@ -25,22 +25,28 @@ extension EntryWidgetView { theme: Theme, showHeader: Bool, sizeConstrainsts: EntryWidget.SizeConstraints, - channels: [EntryWidget.Channel], - channelSelected: @escaping (EntryWidget.Channel) -> Void + channels: Published<[EntryWidget.Channel]>.Publisher, + channelSelected: @escaping (EntryWidget.Channel) throws -> Void ) { self.theme = theme self.sizeConstraints = sizeConstrainsts self.showHeader = showHeader - self.channels = channels self.channelSelected = channelSelected - self.viewState = .offline + self.viewState = .mediaTypes + + channels.assign(to: &self.$channels) } } } extension EntryWidgetView.Model { func selectChannel(_ channel: EntryWidget.Channel) { - channelSelected(channel) + do { + try channelSelected(channel) + } catch { + // TODO: Distinguish errors on View if needed + viewState = .error + } } func onTryAgainTapped() { diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift index 3b6f208ef..6e326e48f 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift @@ -53,6 +53,7 @@ extension Glia { var snackBar: SnackBar var processInfo: ProcessInfoHandling var cameraDeviceManager: CoreSdkClient.GetCameraDeviceManageable + var queuesMonitor: QueuesMonitor } } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift index 9833024d5..803eec224 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift @@ -42,7 +42,14 @@ extension Glia.Environment { conditionalCompilation: .live, snackBar: .live, processInfo: .live, - cameraDeviceManager: { try CoreSdkClient.live.getCameraDeviceManageable() } + cameraDeviceManager: { try CoreSdkClient.live.getCameraDeviceManageable() }, + queuesMonitor: .init( + environment: .init( + listQueues: CoreSdkClient.live.listQueues, + subscribeForQueuesUpdates: CoreSdkClient.live.subscribeForQueuesUpdates, + unsubscribeFromUpdates: CoreSdkClient.live.unsubscribeFromUpdates + ) + ) ) } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift index 11b032803..cbeecfcf2 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift @@ -33,7 +33,8 @@ extension Glia.Environment { conditionalCompilation: .mock, snackBar: .mock, processInfo: .mock(), - cameraDeviceManager: { .mock } + cameraDeviceManager: { .mock }, + queuesMonitor: .mock ) } diff --git a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Environment.swift b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Environment.swift index ebbc05a5b..3dbc0297a 100644 --- a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Environment.swift +++ b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Environment.swift @@ -2,6 +2,8 @@ import Foundation extension QueuesMonitor { struct Environment { - var sdkClient: CoreSdkClient + var listQueues: CoreSdkClient.ListQueues + var subscribeForQueuesUpdates: CoreSdkClient.SubscribeForQueuesUpdates + var unsubscribeFromUpdates: CoreSdkClient.UnsubscribeFromUpdates } } diff --git a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.swift b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Live.swift similarity index 64% rename from GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.swift rename to GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Live.swift index 89d2f13dd..863d52a9d 100644 --- a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.swift +++ b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Live.swift @@ -3,7 +3,7 @@ import GliaCoreSDK import Combine // TODO: Add unit tests in context of MOB-3669 -class QueuesMonitor { +final class QueuesMonitor { enum State { case idle case updated([Queue]) @@ -16,21 +16,19 @@ class QueuesMonitor { private var subscriptionId: String? { _subscriptionId.value } - private var queues: [Queue] { - _queues.value + private var observedQueues: [Queue] { + _observedQueues.value } - + private var _subscriptionId: LockIsolated = .init(nil) - private var _queues: LockIsolated<[Queue]> = .init([]) + private var _observedQueues: LockIsolated<[Queue]> = .init([]) init(environment: Environment) { self.environment = environment - - startMonitoring() } - func startMonitoring() { - environment.sdkClient.listQueues { [weak self] queues, error in + func startMonitoring(queuesIds: [String]) { + environment.listQueues { [weak self] queues, error in guard let self else { return } @@ -40,9 +38,14 @@ class QueuesMonitor { } if let queues { - self._queues.setValue(queues) - self.state = .updated(queues) - self.observeQueuesUpdates(queues) + let integratorsQueues = queues.filter { queuesIds.contains($0.id) } + + let observedQueues = integratorsQueues.isEmpty ? queues.filter { $0.isDefault } : integratorsQueues + + self.state = .updated(observedQueues) + self.observeQueuesUpdates(observedQueues) + + self._observedQueues.setValue(observedQueues) return } } @@ -50,7 +53,7 @@ class QueuesMonitor { func stopMonitoring() { if let subscriptionId { - environment.sdkClient.unsubscribeFromUpdates(subscriptionId) { [weak self] error in + environment.unsubscribeFromUpdates(subscriptionId) { [weak self] error in self?.state = .failed(error) } } @@ -59,7 +62,7 @@ class QueuesMonitor { private extension QueuesMonitor { func updateQueue(_ queue: Queue) { - _queues.withValue { queues in + _observedQueues.withValue { queues in guard let indexToChange = queues.firstIndex(where: { $0.id == queue.id }) else { queues.append(queue) return @@ -70,14 +73,14 @@ private extension QueuesMonitor { func observeQueuesUpdates(_ queues: [Queue]) { let queuesIds = queues.map { $0.id } - let subscriptionId = environment.sdkClient.subscribeForQueuesUpdates(queuesIds) { [weak self] result in + let subscriptionId = environment.subscribeForQueuesUpdates(queuesIds) { [weak self] result in guard let self else { return } switch result { case .success(let queue): self.updateQueue(queue) - self.state = .updated(self.queues) + self.state = .updated(self.observedQueues) return case .failure(let error): self.state = .failed(error) diff --git a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Mock.swift b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Mock.swift new file mode 100644 index 000000000..447ce766b --- /dev/null +++ b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Mock.swift @@ -0,0 +1,13 @@ +#if DEBUG +import Foundation + +extension QueuesMonitor { + static let mock = QueuesMonitor( + environment: .init( + listQueues: { _ in }, + subscribeForQueuesUpdates: { _, _ in UUID().uuidString }, + unsubscribeFromUpdates: { _, _ in } + ) + ) +} +#endif diff --git a/GliaWidgets/Sources/Utilities/CancelBag.swift b/GliaWidgets/Sources/Utilities/CancelBag.swift new file mode 100644 index 000000000..a37fbd5d0 --- /dev/null +++ b/GliaWidgets/Sources/Utilities/CancelBag.swift @@ -0,0 +1,3 @@ +import Combine + +typealias CancelBag = Set diff --git a/GliaWidgetsTests/Glia.Environment.Failing.swift b/GliaWidgetsTests/Glia.Environment.Failing.swift index 009fa1c8a..f931c0946 100644 --- a/GliaWidgetsTests/Glia.Environment.Failing.swift +++ b/GliaWidgetsTests/Glia.Environment.Failing.swift @@ -65,7 +65,8 @@ extension Glia.Environment { cameraDeviceManager: { fail("\(Self.self).cameraDeviceManager") return .failing - } + }, + queuesMonitor: .failing ) } diff --git a/GliaWidgetsTests/QueuesMonitor.Failing.swift b/GliaWidgetsTests/QueuesMonitor.Failing.swift new file mode 100644 index 000000000..ed6f20f84 --- /dev/null +++ b/GliaWidgetsTests/QueuesMonitor.Failing.swift @@ -0,0 +1,11 @@ +@testable import GliaWidgets + +extension QueuesMonitor { + static let failing = QueuesMonitor( + environment: .init( + listQueues: CoreSdkClient.failing.listQueues, + subscribeForQueuesUpdates: CoreSdkClient.failing.subscribeForQueuesUpdates, + unsubscribeFromUpdates: CoreSdkClient.failing.unsubscribeFromUpdates + ) + ) +} diff --git a/Podfile.lock b/Podfile.lock index 7d692765f..2819c4d11 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -22,12 +22,11 @@ DEPENDENCIES: - SwiftLint SPEC REPOS: - https://github.com/CocoaPods/Specs.git: - - SnapshotTesting trunk: - AccessibilitySnapshot - GliaCoreDependency - GliaCoreSDK + - SnapshotTesting - SwiftLint - TwilioVoice - WebRTC-lib diff --git a/TestingApp/ViewController/ViewController.swift b/TestingApp/ViewController/ViewController.swift index 30bb17443..2815b402a 100644 --- a/TestingApp/ViewController/ViewController.swift +++ b/TestingApp/ViewController/ViewController.swift @@ -89,7 +89,7 @@ class ViewController: UIViewController { // Switch control that toggles bubble visibility via deprecated // `Glia.sharedInstance.startEngagement(engagementKind:in:features:sceneProvider:)` @IBOutlet weak var togglingStartEngBubbleSwitch: UISwitch! - + @IBAction private func settingsTapped() { presentSettings() } @@ -330,10 +330,15 @@ extension ViewController { theme: theme, uiConfig: uiConfig, features: features - ) { result in + ) { [weak self] result in + guard let self else { + return + } switch result { case .success: - self.entryWidget = Glia.sharedInstance.getEntryWidget(queueIds: [""]) + self.catchingError { + self.entryWidget = try? Glia.sharedInstance.getEntryWidget(queueIds: [""]) + } completionBlock("SDK has been configured") completion?(.success(()))