From 1866be408c9c52dcd40b18292e64c4b7aaef280d Mon Sep 17 00:00:00 2001 From: BitriseBot Date: Mon, 23 Oct 2023 13:15:26 +0000 Subject: [PATCH 01/12] Increment project version to 2.2.2 --- GliaWidgets.podspec | 2 +- GliaWidgets/Info.plist | 2 +- GliaWidgets/StaticValues.swift | 2 +- GliaWidgetsTests/Info.plist | 2 +- TestingApp/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/GliaWidgets.podspec b/GliaWidgets.podspec index d4fffcf2d..c058fdd09 100644 --- a/GliaWidgets.podspec +++ b/GliaWidgets.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GliaWidgets' - s.version = '2.2.1' + s.version = '2.2.2' s.summary = 'The Glia iOS Widgets library' s.description = 'The Glia Widgets library allows to integrate easily a UI/UX for Glia\'s Digital Customer Service platform' s.homepage = 'https://github.com/salemove/ios-sdk-widgets' diff --git a/GliaWidgets/Info.plist b/GliaWidgets/Info.plist index adbed3f9f..7a03f4926 100644 --- a/GliaWidgets/Info.plist +++ b/GliaWidgets/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.2.1 + 2.2.2 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/GliaWidgets/StaticValues.swift b/GliaWidgets/StaticValues.swift index a341f3ef6..1c6305988 100644 --- a/GliaWidgets/StaticValues.swift +++ b/GliaWidgets/StaticValues.swift @@ -6,5 +6,5 @@ final class StaticValues { /// version cannot be changed by integrators, so this ensures that our code will /// always have the correct version regardless of what our integrators do with /// our plist files. - static let sdkVersion = "2.2.1" + static let sdkVersion = "2.2.2" } diff --git a/GliaWidgetsTests/Info.plist b/GliaWidgetsTests/Info.plist index 14b5eca8c..9d7d975b3 100644 --- a/GliaWidgetsTests/Info.plist +++ b/GliaWidgetsTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.2.1 + 2.2.2 CFBundleVersion 1 diff --git a/TestingApp/Info.plist b/TestingApp/Info.plist index 9eca7dfd7..16678939a 100644 --- a/TestingApp/Info.plist +++ b/TestingApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.2.1 + 2.2.2 CFBundleURLTypes From ebad9d104e9df9d67f7dde44c839b76055bf7dc0 Mon Sep 17 00:00:00 2001 From: Egor Egorov Date: Mon, 23 Oct 2023 16:14:17 +0300 Subject: [PATCH 02/12] Introduce `startEngagementWithConfig` method with queueIds array MOB-2761 --- GliaWidgets/Public/Glia/Glia.Deprecated.swift | 30 +++++++++++++++++-- .../Glia/Glia.RemoteConfiguration.swift | 3 ++ .../ViewController/ViewController.swift | 1 + 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/GliaWidgets/Public/Glia/Glia.Deprecated.swift b/GliaWidgets/Public/Glia/Glia.Deprecated.swift index 96e8c7dd4..e189f635c 100644 --- a/GliaWidgets/Public/Glia/Glia.Deprecated.swift +++ b/GliaWidgets/Public/Glia/Glia.Deprecated.swift @@ -91,7 +91,7 @@ extension Glia { } /// Deprecated, use ``Glia.configure(with:uiConfig:assetsBuilder:completion)`` instead. - @available(*, deprecated, message: "Deprecated, use ``Glia.configure(with:uiConfig:assetsBuilder:completion`` instead.") + @available(*, deprecated, message: "Deprecated, use ``Glia.configure(with:uiConfig:assetsBuilder:completion:)`` instead.") public func configure( with configuration: Configuration, queueId: String, @@ -149,11 +149,11 @@ extension Glia { ) } - /// Deprecated, use ``Deprecated, use ``Glia.configure(with:uiConfig:assetsBuilder:completion`` and ``Glia.startEngagement(engagementKind:in:theme:features:sceneProvider:)`` instead.`` instead. + /// Deprecated, use ``Glia.configure(with:uiConfig:assetsBuilder:completion:)`` and ``Glia.startEngagement(engagementKind:in:theme:features:sceneProvider:)`` instead. @available(*, deprecated, message: """ - Deprecated, use ``Glia.configure(with:uiConfig:assetsBuilder:completion`` and \ + Deprecated, use ``Glia.configure(with:uiConfig:assetsBuilder:completion:)`` and \ ``Glia.startEngagement(engagementKind:in:theme:features:sceneProvider:)`` instead. """ ) @@ -184,6 +184,30 @@ extension Glia { try completion() } } + + /// Deprecated, use ``Glia.startEngagementWithConfig(engagement:in:uiConfig:assetsBuilder:features:sceneProvider:)`` instead. + @available(*, + deprecated, + message: """ + Deprecated, use ``Glia.startEngagementWithConfig(engagement:in:uiConfig:assetsBuilder:features:sceneProvider:)`` instead. + """ + ) + public func startEngagementWithConfig( + engagement: EngagementKind, + uiConfig: RemoteConfiguration, + assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard, + features: Features = .all, + sceneProvider: SceneProvider? = nil + ) throws { + try startEngagementWithConfig( + engagement: engagement, + in: [], + uiConfig: uiConfig, + assetsBuilder: assetsBuilder, + features: features, + sceneProvider: sceneProvider + ) + } } extension Glia.Authentication { diff --git a/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift b/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift index 344555c0e..c63d85c4a 100644 --- a/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift +++ b/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift @@ -6,6 +6,7 @@ extension Glia { /// /// - Parameters: /// - engagementKind: Engagement media type. + /// - in: Queue identifiers /// - uiConfig: Remote UI configuration. /// - assetsBuilder: Provides assets for remote configuration. /// - features: Set of features to be enabled in the SDK. @@ -22,6 +23,7 @@ extension Glia { /// public func startEngagementWithConfig( engagement: EngagementKind, + in queueIds: [String], uiConfig: RemoteConfiguration, assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard, features: Features = .all, @@ -46,6 +48,7 @@ extension Glia { try startEngagement( engagementKind: engagement, + in: queueIds, theme: theme, features: features, sceneProvider: sceneProvider diff --git a/TestingApp/ViewController/ViewController.swift b/TestingApp/ViewController/ViewController.swift index 31192450a..0b30eacc6 100644 --- a/TestingApp/ViewController/ViewController.swift +++ b/TestingApp/ViewController/ViewController.swift @@ -297,6 +297,7 @@ extension ViewController { try? Glia.sharedInstance.startEngagementWithConfig( engagement: kind, + in: [queueId], uiConfig: config ) } From 9fc0e57317cb1e0f41f02a5209b77dc716ecfa3a Mon Sep 17 00:00:00 2001 From: Egor Egorov Date: Sun, 15 Oct 2023 21:31:34 +0300 Subject: [PATCH 03/12] Re-create interactor before engagement start Before these changes when visitor had subsequent engagements, but SDK was configured only once, interactor instance created on configuration step kept incorrect (not initial) state. As a result, SDK started to behave incorrectly. So we decided to not try to find each bug, but re-create interactor each time on start engagement action or requesting visitor code. This commit adds this functionality. --- GliaWidgets.xcodeproj/project.pbxproj | 24 ++++ .../Public/Glia/Glia+StartEngagement.swift | 30 +++-- GliaWidgets/Public/Glia/Glia.Deprecated.swift | 38 ++----- .../Glia/Glia.RemoteConfiguration.swift | 2 +- GliaWidgets/Public/Glia/Glia.swift | 73 ++++++++---- .../CallVisualizer+Action.swift | 7 ++ .../CallVisualizer/CallVisualizer.swift | 3 + .../EngagementCoordinator.swift | 3 +- .../CoreSDKConfigurator.Interface.swift | 25 +++++ .../CoreSDKConfigurator.Mock.swift | 8 ++ .../Glia.Environment.Interface.swift | 1 + .../Glia.Environment.Live.swift | 3 +- .../Glia.Environment.Mock.swift | 3 +- .../Sources/Interactor/Interactor.Mock.swift | 4 +- .../Sources/Interactor/Interactor.swift | 105 ++++++------------ .../Sources/Observable/ObservableValue.swift | 2 +- .../Call/CallViewController.Mock.swift | 12 -- .../CoreSDKConfigurator.Failing.swift | 12 ++ .../Glia.Environment.Failing.swift | 3 +- .../Interactor/Interactor.Failing.swift | 2 +- .../ChatViewModelTests+CustomCard.swift | 2 +- .../ChatViewModelTests+Gva.swift | 6 +- .../ChatViewModelTests+Transferring.swift | 2 +- .../ChatViewModel/ChatViewModelTests.swift | 26 ----- .../Glia/GliaTests+StartEngagement.swift | 72 +++++++++--- GliaWidgetsTests/Sources/Glia/GliaTests.swift | 105 +++++++++++++++--- .../Sources/InteractorTests.swift | 67 +---------- .../ViewController/ViewController.swift | 4 +- 28 files changed, 356 insertions(+), 288 deletions(-) create mode 100644 GliaWidgets/Sources/CallVisualizer/CallVisualizer+Action.swift create mode 100644 GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift create mode 100644 GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Mock.swift create mode 100644 GliaWidgetsTests/CoreSDKConfigurator.Failing.swift diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index 12fa694e6..0be365c56 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -370,6 +370,8 @@ 846A5C4029ED83C50049B29F /* CallVisualizer.Coordinator.DelegateEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846A5C3F29ED83C50049B29F /* CallVisualizer.Coordinator.DelegateEvent.swift */; }; 846A5C4529F6BEFA0049B29F /* GliaTests+StartEngagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846A5C4429F6BEFA0049B29F /* GliaTests+StartEngagement.swift */; }; 846E822828996A5C008EFBF0 /* AlertViewControllerVoiceOverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E822728996A5C008EFBF0 /* AlertViewControllerVoiceOverTests.swift */; }; + 847956362AD96AD7004EF60C /* CoreSDKConfigurator.Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847956352AD96AD7004EF60C /* CoreSDKConfigurator.Interface.swift */; }; + 847956402ADED7A2004EF60C /* CallVisualizer+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8479563F2ADED7A2004EF60C /* CallVisualizer+Action.swift */; }; 847A7643285A1914004044D1 /* FileUploadListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847A7642285A1914004044D1 /* FileUploadListViewModelTests.swift */; }; 8491AF002A6FB44200CC3E72 /* GvaGalleryCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491AEFF2A6FB44200CC3E72 /* GvaGalleryCardCell.swift */; }; 8491AF022A6FBBBA00CC3E72 /* GvaGalleryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491AF012A6FBBBA00CC3E72 /* GvaGalleryListView.swift */; }; @@ -575,6 +577,8 @@ AFEF5C6F29928DB0005C3D8D /* SecureConversations.FileUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFEF5C6E29928DB0005C3D8D /* SecureConversations.FileUploadView.swift */; }; AFEF5C7129929601005C3D8D /* SecureConversations.FilePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFEF5C7029929601005C3D8D /* SecureConversations.FilePreviewView.swift */; }; AFEF5C7429929A8D005C3D8D /* SecureConversations.FileUploadListViewModel.Environment.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFEF5C7329929A8D005C3D8D /* SecureConversations.FileUploadListViewModel.Environment.Failing.swift */; }; + AFF9542A2ADD8DB600C277E0 /* CoreSDKConfigurator.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFF954292ADD8DB600C277E0 /* CoreSDKConfigurator.Mock.swift */; }; + AFF9542C2ADDA10600C277E0 /* CoreSDKConfigurator.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFF9542B2ADDA10600C277E0 /* CoreSDKConfigurator.Failing.swift */; }; B757AD4073B49FF0A17D071D /* Pods_TestingApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE3A89412171262DF9CD8ABA /* Pods_TestingApp.framework */; }; C0175A0F2A55A624001FACDE /* ChatMessagaEntryViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0175A0E2A55A624001FACDE /* ChatMessagaEntryViewTests.swift */; }; C0175A112A55AA3E001FACDE /* ChatMessageEntryView.+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0175A102A55AA3E001FACDE /* ChatMessageEntryView.+Mock.swift */; }; @@ -1106,6 +1110,8 @@ 846A5C3F29ED83C50049B29F /* CallVisualizer.Coordinator.DelegateEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVisualizer.Coordinator.DelegateEvent.swift; sourceTree = ""; }; 846A5C4429F6BEFA0049B29F /* GliaTests+StartEngagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GliaTests+StartEngagement.swift"; sourceTree = ""; }; 846E822728996A5C008EFBF0 /* AlertViewControllerVoiceOverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertViewControllerVoiceOverTests.swift; sourceTree = ""; }; + 847956352AD96AD7004EF60C /* CoreSDKConfigurator.Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSDKConfigurator.Interface.swift; sourceTree = ""; }; + 8479563F2ADED7A2004EF60C /* CallVisualizer+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVisualizer+Action.swift"; sourceTree = ""; }; 847A7642285A1914004044D1 /* FileUploadListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadListViewModelTests.swift; sourceTree = ""; }; 8491AEFF2A6FB44200CC3E72 /* GvaGalleryCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GvaGalleryCardCell.swift; sourceTree = ""; }; 8491AF012A6FBBBA00CC3E72 /* GvaGalleryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GvaGalleryListView.swift; sourceTree = ""; }; @@ -1315,6 +1321,8 @@ AFEF5C6E29928DB0005C3D8D /* SecureConversations.FileUploadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.FileUploadView.swift; sourceTree = ""; }; AFEF5C7029929601005C3D8D /* SecureConversations.FilePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.FilePreviewView.swift; sourceTree = ""; }; AFEF5C7329929A8D005C3D8D /* SecureConversations.FileUploadListViewModel.Environment.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.FileUploadListViewModel.Environment.Failing.swift; sourceTree = ""; }; + AFF954292ADD8DB600C277E0 /* CoreSDKConfigurator.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSDKConfigurator.Mock.swift; sourceTree = ""; }; + AFF9542B2ADDA10600C277E0 /* CoreSDKConfigurator.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSDKConfigurator.Failing.swift; sourceTree = ""; }; B45FBFA4E2F1D31E83A1CC3A /* Pods_GliaWidgets.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GliaWidgets.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C0175A0E2A55A624001FACDE /* ChatMessagaEntryViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagaEntryViewTests.swift; sourceTree = ""; }; C0175A102A55AA3E001FACDE /* ChatMessageEntryView.+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatMessageEntryView.+Mock.swift"; sourceTree = ""; }; @@ -1707,6 +1715,7 @@ 9A3E1D9C27BA7741005634EB /* FoundationBased.Failing.swift */, 84D5B9652A15204400807F92 /* QuickLookBased.Failing.swift */, 9A1992E627D66C7400161AAE /* UIKitBased.Failing.swift */, + AFF9542B2ADDA10600C277E0 /* CoreSDKConfigurator.Failing.swift */, ); path = GliaWidgetsTests; sourceTree = ""; @@ -2065,6 +2074,7 @@ 1A60AFC62566865F00E53F53 /* Coordinator */, 1A60AF6825656C0200E53F53 /* Coordinators */, 75940940298D378A008B173A /* CoreSDKClient */, + 847956342AD96AA8004EF60C /* CoreSDKConfigurator */, 1A1E309425F8CA3400850E68 /* Download */, 1A60B025256806D500E53F53 /* Extensions */, 1A8366AE25FF409B005FE7EE /* File */, @@ -2887,6 +2897,7 @@ 7594096A298D38C2008B173A /* CallVisualizer.BubbleIcon.swift */, 7594097B298D38C2008B173A /* CallVisualizer+Environment.swift */, 7594097C298D38C2008B173A /* CallVisualizer.swift */, + 8479563F2ADED7A2004EF60C /* CallVisualizer+Action.swift */, ); path = CallVisualizer; sourceTree = ""; @@ -3204,6 +3215,15 @@ path = Glia; sourceTree = ""; }; + 847956342AD96AA8004EF60C /* CoreSDKConfigurator */ = { + isa = PBXGroup; + children = ( + 847956352AD96AD7004EF60C /* CoreSDKConfigurator.Interface.swift */, + AFF954292ADD8DB600C277E0 /* CoreSDKConfigurator.Mock.swift */, + ); + path = CoreSDKConfigurator; + sourceTree = ""; + }; 8491AEFE2A6FB40B00CC3E72 /* Gallery */ = { isa = PBXGroup; children = ( @@ -4666,6 +4686,7 @@ 845E2F88283FB49F00C04D56 /* Theme.Survey.BooleanQuestion.Accessibility.swift in Sources */, C06A7584296EC9DC006B69A2 /* NumberSlotStyle.Accessibility.swift in Sources */, 8491AF212A7D1F7900CC3E72 /* ChatView.GvaGallery.swift in Sources */, + 847956402ADED7A2004EF60C /* CallVisualizer+Action.swift in Sources */, AF10ED8D29BA210500E85309 /* SecureConversations.MessagesWithUnreadCountLoader.swift in Sources */, C0D2F04A2992765F00803B47 /* VideoCallView.OperatorImageView.swift in Sources */, 755D186F29A6A6160009F5E8 /* WelcomeStyle+MessageTextViewDisabledStyle.swift in Sources */, @@ -4695,11 +4716,13 @@ 1A60B0042567F25600E53F53 /* Header.swift in Sources */, 1A4674CD25ED08A30078FA1C /* MediaPickerController.swift in Sources */, 1A63B2F6257A469A00508478 /* AlertPresenter.swift in Sources */, + 847956362AD96AD7004EF60C /* CoreSDKConfigurator.Interface.swift in Sources */, 1A0452EA25DBE259000DA0C1 /* MessageButton.swift in Sources */, 9AE0A7602821904400725946 /* FontScaling.Environment.Interface.swift in Sources */, 9A19926627D3BA3A00161AAE /* GCD.Interface.swift in Sources */, 3197F7B829F7C318008EE9F7 /* SecureConversations.CommonEngagementModel.swift in Sources */, 31DD41652A57105400F92612 /* SecureConversations.TranscriptModel.Environment.swift in Sources */, + AFF9542A2ADD8DB600C277E0 /* CoreSDKConfigurator.Mock.swift in Sources */, 1A277A1225FA604E009FE131 /* ChatFileContentView.swift in Sources */, 75B7BD802A39D5A70060794D /* Layoutable.swift in Sources */, 845876AB282A959C007AC3DF /* SingleChoiceQuestionView.Props.Accessibility.swift in Sources */, @@ -4893,6 +4916,7 @@ 7552DFB12A6FB7DF0093519B /* ChatMessageTests.swift in Sources */, 8491AF672AB8707600CC3E72 /* ChatViewModelTests+Transferring.swift in Sources */, AFEF5C7429929A8D005C3D8D /* SecureConversations.FileUploadListViewModel.Environment.Failing.swift in Sources */, + AFF9542C2ADDA10600C277E0 /* CoreSDKConfigurator.Failing.swift in Sources */, 9A3E1D9D27BA7741005634EB /* FoundationBased.Failing.swift in Sources */, 9A3E1D8427B67F1B005634EB /* Helper.swift in Sources */, C0175A0F2A55A624001FACDE /* ChatMessagaEntryViewTests.swift in Sources */, diff --git a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift index dc0106fa9..d2bbc12ae 100644 --- a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift +++ b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift @@ -28,31 +28,27 @@ extension Glia { features: Features = .all, sceneProvider: SceneProvider? = nil ) throws { - // `interactor?.queueIds.isEmpty == false` statement is needed for integrators who uses old interface - // and pass queue identifier through `configuration` function. - guard !queueIds.isEmpty || interactor?.queueIds.isEmpty == false else { throw GliaError.startingEngagementWithNoQueueIdsIsNotAllowed } + guard !queueIds.isEmpty else { throw GliaError.startingEngagementWithNoQueueIdsIsNotAllowed } guard engagement == .none else { throw GliaError.engagementExists } - guard let interactor = self.interactor else { throw GliaError.sdkIsNotConfigured } + guard let configuration = self.configuration else { throw GliaError.sdkIsNotConfigured } if let engagement = environment.coreSdk.getCurrentEngagement(), engagement.source == .callVisualizer { throw GliaError.callVisualizerEngagementExists } - // This check is needed for integrators who uses old interface - // and pass queue identifier through `configuration` function, - // but would not pass queue ids in this method, so SDK would not override - // existed queue id. - if !queueIds.isEmpty { - interactor.queueIds = queueIds - } + // Creates interactor instance + let createdInteractor = setupInteractor( + configuration: configuration, + queueIds: queueIds + ) theme.chat.connect.queue.firstText = companyName( - using: interactor, + using: configuration, themeCompanyName: theme.chat.connect.queue.firstText ) theme.call.connect.queue.firstText = companyName( - using: interactor, + using: configuration, themeCompanyName: theme.call.connect.queue.firstText ) @@ -71,7 +67,7 @@ extension Glia { ) startRootCoordinator( - with: interactor, + with: createdInteractor, viewFactory: viewFactory, sceneProvider: sceneProvider, engagementKind: engagementKind, @@ -80,7 +76,7 @@ extension Glia { } func companyName( - using interactor: Interactor, + using configuration: Configuration, themeCompanyName: String? ) -> String { let companyNameStringKey = "general.company_name" @@ -98,8 +94,8 @@ extension Glia { } // Integrator has not set a company name in the custom locale, // but has set it on the configuration. - else if !interactor.configuration.companyName.isEmpty { - return interactor.configuration.companyName + else if !configuration.companyName.isEmpty { + return configuration.companyName } // Integrator has not set a company name anywhere, use the default. else { diff --git a/GliaWidgets/Public/Glia/Glia.Deprecated.swift b/GliaWidgets/Public/Glia/Glia.Deprecated.swift index e189f635c..5675af6f5 100644 --- a/GliaWidgets/Public/Glia/Glia.Deprecated.swift +++ b/GliaWidgets/Public/Glia/Glia.Deprecated.swift @@ -99,34 +99,12 @@ extension Glia { assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard, completion: (() -> Void)? = nil ) throws { - guard environment.coreSdk.getCurrentEngagement() == nil else { - throw GliaError.configuringDuringEngagementIsNotAllowed - } - self.uiConfig = uiConfig - self.assetsBuilder = assetsBuilder - - let createdInteractor = Interactor( - configuration: configuration, - queueIds: [queueId], - environment: .init(coreSdk: environment.coreSdk, gcd: environment.gcd) + try configure( + with: configuration, + uiConfig: uiConfig, + assetsBuilder: assetsBuilder, + completion: completion ) - - interactor = createdInteractor - - if let callback = completion { - createdInteractor.withConfiguration { [weak createdInteractor, weak self] in - guard let createdInteractor, let self else { return } - - self.stringProviding = .init(getRemoteString: self.environment.coreSdk.localeProvider.getRemoteString) - - createdInteractor.state = self.environment.coreSdk - .getCurrentEngagement()?.engagedOperator - .map(InteractorState.engaged) ?? createdInteractor.state - callback() - } - } - - startObservingInteractorEvents() } /// Deprecated, use ``Glia.startEngagement(engagementKind:in:theme:features:sceneProvider:)`` instead. @@ -168,16 +146,14 @@ extension Glia { let completion = { [weak self] in try self?.startEngagement( engagementKind: engagementKind, + in: [queueID], theme: theme, features: features, sceneProvider: sceneProvider ) } do { - try configure( - with: configuration, - queueId: queueID - ) { + try configure(with: configuration) { try? completion() } } catch GliaError.configuringDuringEngagementIsNotAllowed { diff --git a/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift b/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift index c63d85c4a..9341f171c 100644 --- a/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift +++ b/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift @@ -40,7 +40,7 @@ extension Glia { uiConfig: uiConfig, assetsBuilder: assetsBuilder ) - if let config = interactor?.configuration { + if let config = configuration { theme.showsPoweredBy = !config.isWhiteLabelApp theme.chat.connect.queue.firstText = config.companyName theme.call.connect.queue.firstText = config.companyName diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index 538370f33..74899a9b7 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -94,6 +94,8 @@ public class Glia { var uiConfig: RemoteConfiguration? var assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard + var configuration: Configuration? + init(environment: Environment) { self.environment = environment } @@ -117,30 +119,33 @@ public class Glia { } self.uiConfig = uiConfig self.assetsBuilder = assetsBuilder + self.configuration = configuration - let createdInteractor = Interactor( - configuration: configuration, - queueIds: [], - environment: .init(coreSdk: environment.coreSdk, gcd: environment.gcd) - ) - - interactor = createdInteractor - - if let callback = completion { - createdInteractor.withConfiguration { [weak createdInteractor, weak self] in - guard let createdInteractor, let self else { return } + self.callVisualizer.delegate = { action in + switch action { + case .visitorCodeIsRequested: + self.setupInteractor(configuration: configuration) + } + } - self.stringProviding = .init(getRemoteString: self.environment.coreSdk.localeProvider.getRemoteString) + // TODO: - Non-optional completion will be added in MOB-2784 + do { + try environment.coreSDKConfigurator.configureWithConfiguration(configuration) { [weak self] in + guard let self else { return } + let getRemoteString = self.environment.coreSdk.localeProvider.getRemoteString + self.stringProviding = .init(getRemoteString: getRemoteString) - createdInteractor.state = self.environment.coreSdk - .getCurrentEngagement()?.engagedOperator - .map(InteractorState.engaged) ?? createdInteractor.state + if let engagement = self.environment.coreSdk.getCurrentEngagement(), + engagement.source == .callVisualizer { + self.setupInteractor(configuration: configuration) + } - callback() + completion?() } + } catch { + self.configuration = nil + debugPrint("💥 Core SDK configuration is not valid. Unexpected error='\(error)'.") } - - startObservingInteractorEvents() } /// Minimizes engagement view if ongoing engagement exists. @@ -296,7 +301,7 @@ public class Glia { } } -// MARK: - Private +// MARK: - Internal extension Glia { internal func startObservingInteractorEvents() { @@ -344,4 +349,34 @@ extension Glia { } } } + + @discardableResult + func setupInteractor( + configuration: Configuration, + queueIds: [String] = [] + ) -> Interactor { + let interactor = Interactor( + visitorContext: configuration.visitorContext, + queueIds: queueIds, + environment: .init(coreSdk: environment.coreSdk, gcd: environment.gcd) + ) + + interactor.state = environment.coreSdk + .getCurrentEngagement()?.engagedOperator + .map(InteractorState.engaged) ?? interactor.state + + environment.coreSDKConfigurator.configureWithInteractor(interactor) + self.interactor = interactor + + startObservingInteractorEvents() + return interactor + } +} + +#if DEBUG +extension Glia { + var isConfigured: Bool { + configuration != nil + } } +#endif diff --git a/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Action.swift b/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Action.swift new file mode 100644 index 000000000..2cdf80fd4 --- /dev/null +++ b/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Action.swift @@ -0,0 +1,7 @@ +import Foundation + +extension CallVisualizer { + enum Action { + case visitorCodeIsRequested + } +} diff --git a/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift b/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift index ad8760831..baa971a1c 100644 --- a/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift +++ b/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift @@ -13,6 +13,7 @@ import GliaCoreSDK /// 3. Handling engagement, featuring video calling, screen sharing, and much more in future. public final class CallVisualizer { private var environment: Environment + var delegate: ((Action) -> Void)? lazy var coordinator: Coordinator = { var theme = Theme() if let uiConfig = environment.uiConfig() { @@ -80,6 +81,7 @@ public final class CallVisualizer { /// - Parameter source: The current viewController to present from. /// public func showVisitorCodeViewController(from source: UIViewController) { + delegate?(.visitorCodeIsRequested) coordinator.showVisitorCodeViewController(by: .alert(source)) } @@ -103,6 +105,7 @@ public final class CallVisualizer { into container: UIView, onEngagementAccepted: @escaping () -> Void ) { + delegate?(.visitorCodeIsRequested) coordinator.showVisitorCodeViewController( by: .embedded(container, onEngagementAccepted: onEngagementAccepted) ) diff --git a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift index 5376ac2f8..e9c268e8f 100644 --- a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift +++ b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift @@ -195,7 +195,8 @@ extension EngagementCoordinator { }, endEditing: { viewController.view.endEditing(true) }, updateProps: { viewController.props = $0 }, - onError: { _ in + onError: { [weak self] _ in + guard let self else { return } viewController.presentAlert( with: self.viewFactory.theme.alertConfiguration.unexpectedError ) diff --git a/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift new file mode 100644 index 000000000..80f5fed8d --- /dev/null +++ b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift @@ -0,0 +1,25 @@ +import Foundation + +struct CoreSDKConfigurator { + var configureWithInteractor: CoreSdkClient.ConfigureWithInteractor + var configureWithConfiguration: (Configuration, (() -> Void)?) throws -> Void +} + +extension CoreSDKConfigurator { + static func create(coreSdk: CoreSdkClient) -> Self { + .init( + configureWithInteractor: coreSdk.configureWithInteractor, + configureWithConfiguration: { configuration, completion in + let sdkConfiguration = try CoreSdkClient.Salemove.Configuration( + siteId: configuration.site, + region: configuration.environment.region, + authorizingMethod: configuration.authorizationMethod.coreAuthorizationMethod, + pushNotifications: configuration.pushNotifications.coreSdk + ) + coreSdk.configureWithConfiguration(sdkConfiguration) { + completion?() + } + } + ) + } +} diff --git a/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Mock.swift b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Mock.swift new file mode 100644 index 000000000..df5272a09 --- /dev/null +++ b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Mock.swift @@ -0,0 +1,8 @@ +#if DEBUG +extension CoreSDKConfigurator { + static let mock = CoreSDKConfigurator( + configureWithInteractor: { _ in }, + configureWithConfiguration: { _, _ in } + ) +} +#endif diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift index efb801dd0..cc7a72360 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift @@ -45,6 +45,7 @@ extension Glia { var screenShareHandler: ScreenShareHandler var messagesWithUnreadCountLoaderScheduler: CoreSdkClient.ReactiveSwift.DateScheduler var orientationManager: OrientationManager + var coreSDKConfigurator: CoreSDKConfigurator } } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift index 5d96bd07c..42dff4046 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift @@ -32,7 +32,8 @@ extension Glia.Environment { uiApplication: .live, uiDevice: .live, notificationCenter: .live - )) + )), + coreSDKConfigurator: .create(coreSdk: .live) ) } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift index c4d11734d..fb5213a2c 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift @@ -26,7 +26,8 @@ extension Glia.Environment { createFileUploadListModel: SecureConversations.FileUploadListViewModel.mock(environment:), screenShareHandler: .mock, messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, - orientationManager: .mock() + orientationManager: .mock(), + coreSDKConfigurator: .mock ) } diff --git a/GliaWidgets/Sources/Interactor/Interactor.Mock.swift b/GliaWidgets/Sources/Interactor/Interactor.Mock.swift index 905574cd5..f3fd97d17 100644 --- a/GliaWidgets/Sources/Interactor/Interactor.Mock.swift +++ b/GliaWidgets/Sources/Interactor/Interactor.Mock.swift @@ -3,12 +3,12 @@ import Foundation extension Interactor { static func mock( - configuration: Configuration = .mock(), + visitorContext: Configuration.VisitorContext? = nil, queueId: String = UUID.mock.uuidString, environment: Environment = .mock ) -> Interactor { .init( - configuration: configuration, + visitorContext: visitorContext, queueIds: [queueId], environment: environment ) diff --git a/GliaWidgets/Sources/Interactor/Interactor.swift b/GliaWidgets/Sources/Interactor/Interactor.swift index 95ab3e77e..b6ce22298 100644 --- a/GliaWidgets/Sources/Interactor/Interactor.swift +++ b/GliaWidgets/Sources/Interactor/Interactor.swift @@ -36,7 +36,7 @@ enum InteractorEvent { class Interactor { typealias EventHandler = (InteractorEvent) -> Void - var queueIds: [String] + let queueIds: [String] var engagedOperator: CoreSdkClient.Operator? { switch state { case .engaged(let engagedOperator): @@ -55,12 +55,9 @@ class Interactor { } } - let configuration: Configuration + let visitorContext: Configuration.VisitorContext? var currentEngagement: CoreSdkClient.Engagement? - /// Flag indicating if configuration was already performed. - var isConfigurationPerformed: Bool = false - private var observers = [() -> (AnyObject?, EventHandler)]() private var isEngagementEndedByVisitor = false @@ -74,15 +71,19 @@ class Interactor { var environment: Environment init( - configuration: Configuration, + visitorContext: Configuration.VisitorContext?, queueIds: [String], environment: Environment ) { self.queueIds = queueIds - self.configuration = configuration + self.visitorContext = visitorContext self.environment = environment } + deinit { + print("☠️", Self.self, ObjectIdentifier(self)) + } + func addObserver(_ observer: AnyObject, handler: @escaping EventHandler) { guard !observers.contains(where: { $0().0 === observer }) else { return } observers.append { [weak observer] in (observer, handler) } @@ -104,34 +105,6 @@ class Interactor { } } } - - func withConfiguration(_ action: @escaping () -> Void) { - environment.coreSdk.configureWithInteractor(self) - // Perform configuration only if it was not done previously. - // Otherwise side effects may occur. For example `onHold` callback - // stops being triggered for audio stream. - if isConfigurationPerformed { - // Early out if configuration is already performed. - action() - } else { - // Mark configuration applied and perfrom configuration. - isConfigurationPerformed = true - - do { - let sdkConfiguration = try CoreSdkClient.Salemove.Configuration( - siteId: configuration.site, - region: configuration.environment.region, - authorizingMethod: configuration.authorizationMethod.coreAuthorizationMethod, - pushNotifications: configuration.pushNotifications.coreSdk - ) - environment.coreSdk.configureWithConfiguration(sdkConfiguration) { - action() - } - } catch { - debugPrint("💥 Core SDK configuration is not valid. Unexpected error='\(error)'.") - } - } - } } extension Interactor { @@ -144,37 +117,33 @@ extension Interactor { let options = mediaType == .audio || mediaType == .video ? CoreSdkClient.EngagementOptions(mediaDirection: .twoWay) - : nil - - withConfiguration { [weak self] in - guard let self = self else { return } - - let coreSdkVisitorContext: CoreSdkClient.VisitorContext? = (self.configuration.visitorContext?.assetId) - .map(CoreSdkClient.VisitorContext.AssetId.init(rawValue:)) - .map(CoreSdkClient.VisitorContext.ContextType.assetId) - .map(CoreSdkClient.VisitorContext.init(_:)) - - self.environment.coreSdk.queueForEngagement( - .init( - queueIds: self.queueIds, - visitorContext: coreSdkVisitorContext, - // shouldCloseAllQueues is `true` by default core sdk, - // here it is passed explicitly - shouldCloseAllQueues: true, - mediaType: mediaType, - engagementOptions: options - ) - ) { [weak self] result in - switch result { - case .failure(let error): - self?.state = .ended(.byError) - failure(error) - case .success(let ticket): - if case .enqueueing = self?.state { - self?.state = .enqueued(ticket) - } - success() + : nil + + let coreSdkVisitorContext: CoreSdkClient.VisitorContext? = (self.visitorContext?.assetId) + .map(CoreSdkClient.VisitorContext.AssetId.init(rawValue:)) + .map(CoreSdkClient.VisitorContext.ContextType.assetId) + .map(CoreSdkClient.VisitorContext.init(_:)) + + self.environment.coreSdk.queueForEngagement( + .init( + queueIds: self.queueIds, + visitorContext: coreSdkVisitorContext, + // shouldCloseAllQueues is `true` by default core sdk, + // here it is passed explicitly + shouldCloseAllQueues: true, + mediaType: mediaType, + engagementOptions: options + ) + ) { [weak self] result in + switch result { + case .failure(let error): + self?.state = .ended(.byError) + failure(error) + case .success(let ticket): + if case .enqueueing = self?.state { + self?.state = .enqueued(ticket) } + success() } } } @@ -187,9 +156,7 @@ extension Interactor { messagePayload: CoreSdkClient.SendMessagePayload, completion: @escaping (Result) -> Void ) { - withConfiguration { [weak self] in - self?.environment.coreSdk.sendMessageWithMessagePayload(messagePayload, completion) - } + environment.coreSdk.sendMessageWithMessagePayload(messagePayload, completion) } func endSession( @@ -276,7 +243,7 @@ extension Interactor: CoreSdkClient.Interactable { debugPrint(reason) } } - let coreSdkVisitorContext: CoreSdkClient.VisitorContext? = (self?.configuration.visitorContext?.assetId) + let coreSdkVisitorContext: CoreSdkClient.VisitorContext? = (self?.visitorContext?.assetId) .map(CoreSdkClient.VisitorContext.AssetId.init(rawValue:)) .map(CoreSdkClient.VisitorContext.ContextType.assetId) .map(CoreSdkClient.VisitorContext.init(_:)) diff --git a/GliaWidgets/Sources/Observable/ObservableValue.swift b/GliaWidgets/Sources/Observable/ObservableValue.swift index 456b6c481..a31e7d4a5 100644 --- a/GliaWidgets/Sources/Observable/ObservableValue.swift +++ b/GliaWidgets/Sources/Observable/ObservableValue.swift @@ -39,7 +39,7 @@ class ObservableValue { // was that `update` closure // must always run on main queue, I added // this check. But we must use some - // battle-tested solutuion like ReactiveSwift or Combine. + // battle-tested solution like ReactiveSwift or Combine. // That will allow us to use proper schedulers for UI, unit tests etc. if Thread.isMainThread { update(new, old) diff --git a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift index f0d35516e..8e5d6d78b 100644 --- a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift @@ -19,11 +19,9 @@ extension CallViewController { } static func mockAudioCallQueueState() throws -> CallViewController { - let conf = Configuration.mock() let queueId = UUID.mock.uuidString let interactorEnv = Interactor.Environment.mock let interactor = Interactor.mock( - configuration: conf, queueId: queueId, environment: interactorEnv ) @@ -55,11 +53,9 @@ extension CallViewController { } static func mockAudioCallConnectingState() throws -> CallViewController { - let conf = Configuration.mock() let queueId = UUID.mock.uuidString let interactorEnv = Interactor.Environment.mock let interactor = Interactor.mock( - configuration: conf, queueId: queueId, environment: interactorEnv ) @@ -90,14 +86,12 @@ extension CallViewController { } static func mockAudioCallConnectedState() throws -> CallViewController { - let conf = Configuration.mock() let queueId = UUID.mock.uuidString var interactorEnv = Interactor.Environment.mock interactorEnv.coreSdk.configureWithConfiguration = { _, callback in callback?() } let interactor = Interactor.mock( - configuration: conf, queueId: queueId, environment: interactorEnv ) @@ -146,11 +140,9 @@ extension CallViewController { } static func mockVideoCallConnectingState() throws -> CallViewController { - let conf = Configuration.mock() let queueId = UUID.mock.uuidString let interactorEnv = Interactor.Environment.mock let interactor = Interactor.mock( - configuration: conf, queueId: queueId, environment: interactorEnv ) @@ -194,11 +186,9 @@ extension CallViewController { } static func mockVideoCallQueueState() throws -> CallViewController { - let conf = Configuration.mock() let queueId = UUID.mock.uuidString let interactorEnv = Interactor.Environment.mock let interactor = Interactor.mock( - configuration: conf, queueId: queueId, environment: interactorEnv ) @@ -230,11 +220,9 @@ extension CallViewController { } static func mockVideoCallConnectedState() throws -> CallViewController { - let conf = Configuration.mock() let queueId = UUID.mock.uuidString let interactorEnv = Interactor.Environment.mock let interactor = Interactor.mock( - configuration: conf, queueId: queueId, environment: interactorEnv ) diff --git a/GliaWidgetsTests/CoreSDKConfigurator.Failing.swift b/GliaWidgetsTests/CoreSDKConfigurator.Failing.swift new file mode 100644 index 000000000..1ac46ef28 --- /dev/null +++ b/GliaWidgetsTests/CoreSDKConfigurator.Failing.swift @@ -0,0 +1,12 @@ +@testable import GliaWidgets + +extension CoreSDKConfigurator { + static let failing = Self.init( + configureWithInteractor: { _ in + fail("\(Self.self).configureWithInteractor") + }, + configureWithConfiguration: { _, _ in + fail("\(Self.self).configureWithConfiguration") + } + ) +} diff --git a/GliaWidgetsTests/Glia.Environment.Failing.swift b/GliaWidgetsTests/Glia.Environment.Failing.swift index 14ba7bcff..dc85373dd 100644 --- a/GliaWidgetsTests/Glia.Environment.Failing.swift +++ b/GliaWidgetsTests/Glia.Environment.Failing.swift @@ -55,7 +55,8 @@ extension Glia.Environment { }, screenShareHandler: .mock, messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, - orientationManager: .mock() + orientationManager: .mock(), + coreSDKConfigurator: .failing ) } diff --git a/GliaWidgetsTests/Interactor/Interactor.Failing.swift b/GliaWidgetsTests/Interactor/Interactor.Failing.swift index 3d7c22806..a3fab89e3 100644 --- a/GliaWidgetsTests/Interactor/Interactor.Failing.swift +++ b/GliaWidgetsTests/Interactor/Interactor.Failing.swift @@ -3,7 +3,7 @@ import Foundation extension Interactor { static let failing = Interactor( - configuration: .mock(), + visitorContext: nil, queueIds: ["mocked-id"], environment: .failing ) diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift index c33029aea..80ee11a0b 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift @@ -21,7 +21,7 @@ extension ChatViewModelTests { } let interactorMock = Interactor.mock(environment: interactorEnv) interactorMock.state = .engaged(nil) - interactorMock.isConfigurationPerformed = true +// interactorMock.isConfigurationPerformed = true var env = ChatViewModel.Environment.failing() env.fileManager.fileExistsAtPath = { _ in true } diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift index 805b1dbdc..ba7265e22 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift @@ -58,7 +58,7 @@ extension ChatViewModelTests { } let interactorMock = Interactor.mock(environment: interactorEnv) interactorMock.state = .engaged(nil) - interactorMock.isConfigurationPerformed = true +// interactorMock.isConfigurationPerformed = true viewModel = .mock(interactor: interactorMock, environment: env) @@ -80,7 +80,7 @@ extension ChatViewModelTests { let interactorMock = Interactor.mock(environment: interactorEnv) interactorMock.state = .none - interactorMock.isConfigurationPerformed = true +// interactorMock.isConfigurationPerformed = true var env = ChatViewModel.Environment.failing() env.fileManager.fileExistsAtPath = { _ in true } @@ -136,7 +136,7 @@ extension ChatViewModelTests { } let interactorMock = Interactor.mock(environment: interactorEnv) interactorMock.state = .engaged(nil) - interactorMock.isConfigurationPerformed = true +// interactorMock.isConfigurationPerformed = true let viewModel = ChatViewModel.mock(interactor: interactorMock, environment: env) let messagesSectionIndex = 3 diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift index 008911f97..72a47faa0 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift @@ -20,7 +20,7 @@ extension ChatViewModelTests { interactorEnv.coreSdk.queueForEngagement = { _, _ in } interactorEnv.coreSdk.configureWithInteractor = { _ in } let interactorMock = Interactor.mock(environment: interactorEnv) - interactorMock.isConfigurationPerformed = true +// interactorMock.isConfigurationPerformed = true var env = ChatViewModel.Environment.failing() env.fileManager.fileExistsAtPath = { _ in true } diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift index 64593b0a2..0f3675862 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift @@ -111,32 +111,6 @@ class ChatViewModelTests: XCTestCase { ) XCTAssertEqual(chatType, .nonAuthenticated) } - - func test__startCallsSDKConfigureWithInteractorAndСonfigureWithConfiguration() throws { - var interactorEnv = Interactor.Environment.init( - coreSdk: .failing, - gcd: .mock - ) - enum Calls { - case configureWithConfiguration, configureWithInteractor - } - var calls: [Calls] = [] - interactorEnv.coreSdk.configureWithConfiguration = { _, _ in - calls.append(.configureWithConfiguration) - } - interactorEnv.coreSdk.configureWithInteractor = { _ in - calls.append(.configureWithInteractor) - } - let interactor = Interactor.mock(environment: interactorEnv) - var viewModelEnv = ChatViewModel.Environment.failing(fetchChatHistory: { $0(.success([])) }) - viewModelEnv.createFileUploadListModel = { _ in .mock() } - viewModelEnv.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] } - viewModelEnv.fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in } - viewModelEnv.loadChatMessagesFromHistory = { true } - let viewModel = ChatViewModel.mock(interactor: interactor, environment: viewModelEnv) - viewModel.start() - XCTAssertEqual(calls, [.configureWithInteractor, .configureWithConfiguration]) - } func test_onInteractorStateEngagedClearsChatQueueSection() throws { var viewModelEnv = ChatViewModel.Environment.failing() diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift index df6064d91..4ca456bde 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift @@ -9,22 +9,38 @@ extension GliaTests { } func testStartEngagementThrowsErrorWhenEngagementAlreadyExists() throws { - let sdk = Glia(environment: .failing) + var sdkEnv = Glia.Environment.failing + sdkEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + let sdk = Glia(environment: sdkEnv) sdk.rootCoordinator = .mock(engagementKind: .chat, screenShareHandler: .mock) - try sdk.configure(with: .mock(), queueId: "queueID") + try sdk.configure(with: .mock()) - XCTAssertThrowsError(try sdk.startEngagement(engagementKind: .chat)) { error in + XCTAssertThrowsError( + try sdk.startEngagement( + engagementKind: .chat, + in: ["queueID"] + ) + ) { error in XCTAssertEqual(error as? GliaError, GliaError.engagementExists) } } func testStartEngagementThrowsErrorDuringActiveCallVisualizerEngagement() throws { let sdk = Glia(environment: .failing) - try sdk.configure(with: .mock(), queueId: "queueID") - + sdk.environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + try sdk.configure(with: .mock()) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } - XCTAssertThrowsError(try sdk.startEngagement(engagementKind: .chat)) { error in + XCTAssertThrowsError( + try sdk.startEngagement( + engagementKind: .chat, + in: ["queueID"] + ) + ) { error in XCTAssertEqual(error as? GliaError, GliaError.callVisualizerEngagementExists) } } @@ -32,7 +48,12 @@ extension GliaTests { func testStartEngagementThrowsErrorWhenSdkHasNoQueueIds() { let sdk = Glia(environment: .failing) - XCTAssertThrowsError(try sdk.startEngagement(engagementKind: .chat)) { error in + XCTAssertThrowsError( + try sdk.startEngagement( + engagementKind: .chat, + in: [] + ) + ) { error in XCTAssertEqual(error as? GliaError, GliaError.startingEngagementWithNoQueueIdsIsNotAllowed) } } @@ -41,19 +62,23 @@ extension GliaTests { enum Call { case configureWithInteractor, configureWithConfiguration } var calls: [Call] = [] var environment = Glia.Environment.failing - environment.coreSdk.configureWithInteractor = { _ in + environment.coreSDKConfigurator.configureWithInteractor = { _ in calls.append(.configureWithInteractor) } - environment.coreSdk.configureWithConfiguration = { _, _ in + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in calls.append(.configureWithConfiguration) + completion?() + } + environment.coreSdk.localeProvider.getRemoteString = { _ in nil } + environment.createRootCoordinator = { _, _, _, _, _, _, _ in + .mock() } let sdk = Glia(environment: environment) try sdk.start(.chat, configuration: .mock(), queueID: "queueId", visitorContext: nil) - let interactor = try XCTUnwrap(sdk.interactor) - XCTAssertTrue(interactor.isConfigurationPerformed) - XCTAssertEqual(calls, [.configureWithInteractor, .configureWithConfiguration]) + XCTAssertTrue(sdk.isConfigured) + XCTAssertEqual(calls, [.configureWithConfiguration, .configureWithInteractor]) } func testCompanyNameIsReceivedFromTheme() throws { @@ -73,6 +98,11 @@ extension GliaTests { environment: .failing ) } + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } + environment.coreSdk.localeProvider.getRemoteString = { _ in nil } let sdk = Glia(environment: environment) @@ -107,10 +137,10 @@ extension GliaTests { } environment.coreSdk.localeProvider.getRemoteString = { _ in "Glia" } - environment.coreSdk.configureWithInteractor = { _ in } - environment.coreSdk.configureWithConfiguration = { _, completion in + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in completion?() } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.getCurrentEngagement = { nil } let sdk = Glia(environment: environment) @@ -145,6 +175,11 @@ extension GliaTests { environment: .failing ) } + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } + environment.coreSdk.localeProvider.getRemoteString = { _ in nil } let sdk = Glia(environment: environment) @@ -173,6 +208,11 @@ extension GliaTests { environment: .failing ) } + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } + environment.coreSdk.localeProvider.getRemoteString = { _ in nil } let sdk = Glia(environment: environment) @@ -203,10 +243,10 @@ extension GliaTests { } environment.coreSdk.localeProvider.getRemoteString = { _ in "" } - environment.coreSdk.configureWithInteractor = { _ in } - environment.coreSdk.configureWithConfiguration = { _, completion in + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in completion?() } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.getCurrentEngagement = { nil } let sdk = Glia(environment: environment) diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests.swift b/GliaWidgetsTests/Sources/Glia/GliaTests.swift index 46dcde9ca..5f2020671 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests.swift @@ -55,6 +55,10 @@ final class GliaTests: XCTestCase { gliaEnv.coreSdk.queueForEngagement = { _, callback in callback(.success(.mock)) } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } gliaEnv.uuid = { .mock } gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } @@ -92,7 +96,6 @@ final class GliaTests: XCTestCase { let sdk = Glia(environment: gliaEnv) let kind = EngagementKind.audioCall - let site = "site" let queueID = "queueID" let visitorContext = VisitorContext.mock() @@ -128,16 +131,17 @@ final class GliaTests: XCTestCase { var gliaEnv = Glia.Environment.failing gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } let sdk = Glia(environment: gliaEnv) sdk.onEvent = { calls.append(.onEvent($0)) } - try sdk.configure( - with: .mock(), - queueId: "queueId" - ) - + try sdk.configure(with: .mock()) + sdk.callVisualizer.delegate?(.visitorCodeIsRequested) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } sdk.interactor?.state = .engaged(nil) @@ -155,15 +159,17 @@ final class GliaTests: XCTestCase { gliaEnv.coreSdk.configureWithInteractor = { _ in } gliaEnv.coreSdk.configureWithConfiguration = { _, _ in } gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } let sdk = Glia(environment: gliaEnv) sdk.onEvent = { calls.append(.onEvent($0)) } - try sdk.configure( - with: .mock(), - queueId: "queueId" - ) + try sdk.configure(with: .mock()) + sdk.callVisualizer.delegate?(.visitorCodeIsRequested) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } sdk.interactor?.state = .ended(.byOperator) @@ -180,15 +186,17 @@ final class GliaTests: XCTestCase { var gliaEnv = Glia.Environment.failing gliaEnv.callVisualizerPresenter = .init(presenter: { nil }) gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } let sdk = Glia(environment: gliaEnv) sdk.onEvent = { calls.append(.onEvent($0)) } - try sdk.configure( - with: .mock(), - queueId: "queueId" - ) + try sdk.configure(with: .mock()) + sdk.callVisualizer.delegate?(.visitorCodeIsRequested) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } sdk.callVisualizer.coordinator.showEndScreenSharingViewController() @@ -209,14 +217,17 @@ final class GliaTests: XCTestCase { gliaEnv.callVisualizerPresenter = .init(presenter: { nil }) gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } gliaEnv.notificationCenter.addObserverClosure = { _, _, _, _ in } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } + let sdk = Glia(environment: gliaEnv) sdk.onEvent = { calls.append(.onEvent($0)) } - try sdk.configure( - with: .mock(), - queueId: "queueId" - ) + try sdk.configure(with: .mock()) + sdk.callVisualizer.delegate?(.visitorCodeIsRequested) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } sdk.callVisualizer.coordinator.showVideoCallViewController() @@ -283,4 +294,62 @@ final class GliaTests: XCTestCase { XCTAssertEqual(delegate.invokedEventCallParameter, .maximized) XCTAssertEqual(delegate.invokedEventCallParameterList, [.maximized]) } + + func test_isConfiguredIsInitiallyFalse() { + XCTAssertFalse(Glia(environment: .failing).isConfigured) + } + + func test_isConfiguredIsTrueWhenConfigurationPerformed() throws { + var environment = Glia.Environment.failing + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion?() + } + let sdk = Glia(environment: environment) + try sdk.configure(with: .mock()) + XCTAssertTrue(sdk.isConfigured) + } + + func test_isConfiguredIsFalseWhenConfigureWithConfigurationThrowsError() throws { + var environment = Glia.Environment.failing + environment.coreSDKConfigurator.configureWithConfiguration = { _, _ in + throw CoreSdkClient.GliaCoreError.mock() + } + let sdk = Glia(environment: environment) + try sdk.configure(with: .mock()) + XCTAssertFalse(sdk.isConfigured) + } + + func test_engagementCoordinatorGetsDeallocated() throws { + var environment = Glia.Environment.failing + environment.coreSDKConfigurator.configureWithConfiguration = { + _, callback in + callback?() + } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } + environment.coreSdk.localeProvider.getRemoteString = { _ in nil } + environment.createRootCoordinator = { _, _, _, _, _, _, _ in .mock() } + let configuration = Configuration.mock() + + let sdk = Glia(environment: environment) + enum Call { + case configureWithConfiguration + } + var calls: [Call] = [] + try sdk.configure(with: configuration) { + calls.append(.configureWithConfiguration) + } + try sdk.startEngagement( + engagementKind: .chat, + in: ["mockedQueueId"] + ) + weak var rootCoordinator = sdk.rootCoordinator + XCTAssertNotNil(rootCoordinator) + var endEngagementResult: Result? + sdk.endEngagement { result in + endEngagementResult = result + } + XCTAssertNoThrow(try XCTUnwrap(endEngagementResult)) + XCTAssertNil(sdk.rootCoordinator) + XCTAssertNil(rootCoordinator) + } } diff --git a/GliaWidgetsTests/Sources/InteractorTests.swift b/GliaWidgetsTests/Sources/InteractorTests.swift index 887f719f3..515a1cfc7 100644 --- a/GliaWidgetsTests/Sources/InteractorTests.swift +++ b/GliaWidgetsTests/Sources/InteractorTests.swift @@ -19,23 +19,16 @@ class InteractorTests: XCTestCase { func test__enqueueForEngagement() throws { enum Call { - case configureWithConfiguration, configureWithInteractor, queueForEngagement + case queueForEngagement } var coreSdkCalls = [Call]() var coreSdk = CoreSdkClient.failing - coreSdk.configureWithConfiguration = { _, completion in - coreSdkCalls.append(.configureWithConfiguration) - completion?() - } - coreSdk.configureWithInteractor = { _ in - coreSdkCalls.append(.configureWithInteractor) - } coreSdk.queueForEngagement = { _, _ in coreSdkCalls.append(.queueForEngagement) } interactor = .init( - configuration: mock.config, + visitorContext: nil, queueIds: [mock.queueId], environment: .init(coreSdk: coreSdk, gcd: .failing) ) @@ -44,59 +37,7 @@ class InteractorTests: XCTestCase { XCTFail($0.reason) } - XCTAssertEqual(coreSdkCalls, [ - .configureWithInteractor, - .configureWithConfiguration, - .queueForEngagement - ]) - } - - func test__isConfigurationPerformedIsInitiallyFalse() { - XCTAssertFalse(Interactor.mock(environment: .failing).isConfigurationPerformed) - } - - func test__isConfigurationPerformedBecomesTrue() throws { - var interactorEnv = Interactor.Environment.failing - interactorEnv.coreSdk.configureWithInteractor = { _ in } - interactorEnv.coreSdk.configureWithConfiguration = { _, _ in } - let interactor = Interactor.mock(environment: interactorEnv) - interactor.withConfiguration {} - XCTAssertTrue(interactor.isConfigurationPerformed) - } - - func test__configureWithConfigurationPerformedOnce() { - enum Call { - case configureWithConfiguration - } - var calls: [Call] = [] - var interactorEnv = Interactor.Environment.failing - interactorEnv.coreSdk.configureWithInteractor = { _ in } - interactorEnv.coreSdk.configureWithConfiguration = { _, _ in - calls.append(.configureWithConfiguration) - } - let interactor = Interactor.mock(environment: interactorEnv) - for _ in 0 ..< 1000 { - interactor.withConfiguration {} - } - XCTAssertEqual(calls, [.configureWithConfiguration]) - } - - func test_withConfigurationInvokesCompletionForFirstAndNextCalls() { - enum Callback { - case withConfiguration - } - var callbacks: [Callback] = [] - var interactorEnv = Interactor.Environment.failing - interactorEnv.coreSdk.configureWithInteractor = { _ in } - interactorEnv.coreSdk.configureWithConfiguration = { $1?() } - let interactor = Interactor.mock(environment: interactorEnv) - for _ in 0 ..< 3 { - interactor.withConfiguration { - callbacks.append(.withConfiguration) - } - } - - XCTAssertEqual(callbacks, [.withConfiguration, .withConfiguration, .withConfiguration]) + XCTAssertEqual(coreSdkCalls, [.queueForEngagement]) } func test_onEngagementTransfer() throws { @@ -109,7 +50,7 @@ class InteractorTests: XCTestCase { let mockOperator: CoreSdkClient.Operator = .mock() interactor = .init( - configuration: mock.config, + visitorContext: nil, queueIds: [mock.queueId], environment: .init(coreSdk: .failing, gcd: .mock) ) diff --git a/TestingApp/ViewController/ViewController.swift b/TestingApp/ViewController/ViewController.swift index 0b30eacc6..9f9d0d8e2 100644 --- a/TestingApp/ViewController/ViewController.swift +++ b/TestingApp/ViewController/ViewController.swift @@ -56,10 +56,8 @@ class ViewController: UIViewController { } @IBAction private func chatTapped() { - do { + catchingError { try presentGlia(.chat) - } catch { - showErrorAlert(using: error) } } From 73d03b16b94fc763e58c9036f16c245b47f6cb4a Mon Sep 17 00:00:00 2001 From: Egor Egorov Date: Tue, 24 Oct 2023 13:05:08 +0300 Subject: [PATCH 04/12] Improve error handling in Testing app --- TestingApp/ViewController/ViewController.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/TestingApp/ViewController/ViewController.swift b/TestingApp/ViewController/ViewController.swift index 9f9d0d8e2..2d28283ca 100644 --- a/TestingApp/ViewController/ViewController.swift +++ b/TestingApp/ViewController/ViewController.swift @@ -92,16 +92,17 @@ class ViewController: UIViewController { private func showErrorAlert(using error: Error) { guard let gliaError = error as? GliaError else { return } - switch error { + switch gliaError { case GliaError.engagementExists: alert(message: "Failed to start\nEngagement is ongoing, please use 'Resume' button") case GliaError.engagementNotExist: alert(message: "Failed to start\nNo ongoing engagement. Please start a new one with 'Start chat' button") case GliaError.callVisualizerEngagementExists: alert(message: "Failed to start\nCall Visualizer engagement is ongoing") + case GliaError.configuringDuringEngagementIsNotAllowed: + alert(message: "The operation couldn't be completed. '\(gliaError)'.") default: alert(message: "Failed to start\nCheck Glia parameters in Settings") - } } @@ -118,7 +119,10 @@ class ViewController: UIViewController { if let fileName = fileName { config = self?.retrieveRemoteConfiguration(fileName) } - self?.configureSDK(uiConfig: config) + self?.configureSDK(uiConfig: config) { [weak self] result in + guard case let .failure(error) = result else { return } + self?.showErrorAlert(using: error) + } } } @@ -194,13 +198,11 @@ extension ViewController { configuration.pushNotifications = pushNotifications let startEngagement = { - do { + self.catchingError { try Glia.sharedInstance.startEngagement( engagementKind: engagementKind, in: [self.queueId] ) - } catch { - self.showErrorAlert(using: error) } } From 2ab12d0c54288484627c82ac8f258a7bd6430f3c Mon Sep 17 00:00:00 2001 From: Egor Egorov Date: Tue, 24 Oct 2023 14:54:09 +0300 Subject: [PATCH 05/12] Make Glia.configuration private(set) --- GliaWidgets/Public/Glia/Glia.swift | 3 ++- GliaWidgets/Sources/Interactor/Interactor.swift | 4 ---- .../Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift | 1 - .../Sources/ChatViewModel/ChatViewModelTests+Gva.swift | 3 --- .../ChatViewModel/ChatViewModelTests+Transferring.swift | 1 - GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift | 4 ++-- 6 files changed, 4 insertions(+), 12 deletions(-) diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index 74899a9b7..7b2658f3e 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -94,7 +94,7 @@ public class Glia { var uiConfig: RemoteConfiguration? var assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard - var configuration: Configuration? + private(set) var configuration: Configuration? init(environment: Environment) { self.environment = environment @@ -375,6 +375,7 @@ extension Glia { #if DEBUG extension Glia { + /// Used for unit tests only var isConfigured: Bool { configuration != nil } diff --git a/GliaWidgets/Sources/Interactor/Interactor.swift b/GliaWidgets/Sources/Interactor/Interactor.swift index b6ce22298..7873b4d39 100644 --- a/GliaWidgets/Sources/Interactor/Interactor.swift +++ b/GliaWidgets/Sources/Interactor/Interactor.swift @@ -80,10 +80,6 @@ class Interactor { self.environment = environment } - deinit { - print("☠️", Self.self, ObjectIdentifier(self)) - } - func addObserver(_ observer: AnyObject, handler: @escaping EventHandler) { guard !observers.contains(where: { $0().0 === observer }) else { return } observers.append { [weak observer] in (observer, handler) } diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift index 80ee11a0b..a8fa5d420 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift @@ -21,7 +21,6 @@ extension ChatViewModelTests { } let interactorMock = Interactor.mock(environment: interactorEnv) interactorMock.state = .engaged(nil) -// interactorMock.isConfigurationPerformed = true var env = ChatViewModel.Environment.failing() env.fileManager.fileExistsAtPath = { _ in true } diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift index ba7265e22..2f61e91e0 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Gva.swift @@ -58,7 +58,6 @@ extension ChatViewModelTests { } let interactorMock = Interactor.mock(environment: interactorEnv) interactorMock.state = .engaged(nil) -// interactorMock.isConfigurationPerformed = true viewModel = .mock(interactor: interactorMock, environment: env) @@ -80,7 +79,6 @@ extension ChatViewModelTests { let interactorMock = Interactor.mock(environment: interactorEnv) interactorMock.state = .none -// interactorMock.isConfigurationPerformed = true var env = ChatViewModel.Environment.failing() env.fileManager.fileExistsAtPath = { _ in true } @@ -136,7 +134,6 @@ extension ChatViewModelTests { } let interactorMock = Interactor.mock(environment: interactorEnv) interactorMock.state = .engaged(nil) -// interactorMock.isConfigurationPerformed = true let viewModel = ChatViewModel.mock(interactor: interactorMock, environment: env) let messagesSectionIndex = 3 diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift index 72a47faa0..bf390e669 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift @@ -20,7 +20,6 @@ extension ChatViewModelTests { interactorEnv.coreSdk.queueForEngagement = { _, _ in } interactorEnv.coreSdk.configureWithInteractor = { _ in } let interactorMock = Interactor.mock(environment: interactorEnv) -// interactorMock.isConfigurationPerformed = true var env = ChatViewModel.Environment.failing() env.fileManager.fileExistsAtPath = { _ in true } diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift index 4ca456bde..cd49506ad 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift @@ -282,8 +282,8 @@ extension GliaTests { } environment.coreSdk.localeProvider.getRemoteString = { _ in "" } - environment.coreSdk.configureWithInteractor = { _ in } - environment.coreSdk.configureWithConfiguration = { _, completion in + environment.coreSDKConfigurator.configureWithInteractor = { _ in } + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in // Simulating what happens in the Widgets when the configuration gets done Glia.sharedInstance.stringProviding = StringProviding( getRemoteString: environment.coreSdk.localeProvider.getRemoteString From 744af0e9c0dc8d3850274085cf6f7f0d291ef938 Mon Sep 17 00:00:00 2001 From: Gerson Noboa Date: Wed, 25 Oct 2023 16:19:16 +0300 Subject: [PATCH 06/12] Add updating the widgets version in Cortex Financial when releasing The new version of the Widgets SDK, which is represented in the Bitrise workflow as `$NEW_VERSION`, is sent to a script that executes the `update_dependencies` workflow in Cortex Financial. Then, in the Cortex side, the version of the widgets will be increased and a new PR made. As this is part of the `deployment` workflow for the widgets, this means that upon releasing widgets, nothing needs to be done from our side to update Cortex (apart from approving the PR in Cortex). MOB-2783 --- bitrise.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/bitrise.yml b/bitrise.yml index 60029ccfe..8082d1a68 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -89,6 +89,7 @@ workflows: - cache-push@2: {} after_run: - _increment_project_version + - increment_widgets_version_in_cortex_financial development-build: steps: - activate-ssh-key@4: @@ -126,6 +127,40 @@ workflows: - webhook_url: $SLACK_IOS_WEBHOOK - cache-push@2: {} description: Workflow for creating builds of development branch. Triggered on each push to development branch, notifies over email + Slack (external) + increment_widgets_version_in_cortex_financial: + steps: + - script@1: + inputs: + - content: |- + #!/usr/bin/env bash + # fail if any commands fails + set -e + # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfully + set -o pipefail + # debug log + set -x + + curl https://app.bitrise.io/app/$CORTEX_DEMO_APP_SLUG/build/start.json -L --data \ + '{ + "hook_info": { + "type": "bitrise", + "build_trigger_token": "'"$CORTEX_DEMO_BUILD_TRIGGER_TOKEN"'" + }, + "build_params": { + "branch": "master", + "workflow_id": "update_dependencies", + "environments": [{ + "mapped_to": "VERSION", + "value": "'"$NEW_VERSION"'", + "is_expand": false + }] + }, + "triggered_by":"curl" + }' + envs: + - opts: + is_expand: false + VERSION_INCREMENT_TYPE: patch pull-request: steps: - activate-ssh-key@4: From fe8ac10de58182019bc54c3d3d31b2238cfbd1e5 Mon Sep 17 00:00:00 2001 From: Egor Egorov Date: Wed, 25 Oct 2023 17:57:16 +0300 Subject: [PATCH 07/12] Proximity meter fixes There was the issue when Proximity meter is kept on after having audio/video call. The problem was that `isProximityMonitoringEnabled` was not toggled back. The Proximity meter blocks user interactions and turns screen brightness off/on itself. So some redundant part of ProximityManager class was removed. MOB-2771 --- GliaWidgets.xcodeproj/project.pbxproj | 16 ++++++++ .../Public/Glia/Glia+StartEngagement.swift | 4 +- GliaWidgets/Public/Glia/Glia.swift | 3 +- .../SecureConversations.Coordinator.swift | 5 ++- .../CallVisualizer+Environment.swift | 1 + .../CallVisualizer/CallVisualizer.swift | 3 +- ...llVisualizer.Coordinator.Environment.swift | 1 + .../CallVisualizer.Coordinator.swift | 3 +- .../VideoCallCoordinator.Environment.swift | 1 + .../Coordinator/VideoCallCoordinator.swift | 6 +-- .../VideoCallViewController.swift | 20 +--------- .../VideoCallViewModel.Environment.swift | 1 + .../ViewModel/VideoCallViewModel.swift | 9 ++++- .../Coordinators/Call/CallCoordinator.swift | 8 ++-- .../Chat/ChatCoordinator.Environment.swift | 1 + .../Coordinators/Chat/ChatCoordinator.swift | 3 +- ...gagementCoordinator.Environment.Mock.swift | 4 +- .../EngagementCoordinator.Environment.swift | 2 +- .../EngagementCoordinator.swift | 11 +++--- .../Glia.Environment.Interface.swift | 1 + .../Glia.Environment.Live.swift | 6 ++- .../Glia.Environment.Mock.swift | 3 +- .../ProximityManager.Mock.swift | 12 ++++++ .../ProximityManager/ProximityManager.swift | 38 ++----------------- .../UIKitBased/UIKitBased.Interface.swift | 2 - .../Sources/UIKitBased/UIKitBased.Live.swift | 2 - .../Sources/UIKitBased/UIKitBased.Mock.swift | 2 - .../Call/CallViewController.Mock.swift | 3 -- .../Call/CallViewController.swift | 15 -------- .../Chat/ChatViewController.Mock.swift | 5 --- .../ViewModel/Call/CallViewModel.swift | 5 +++ .../ChatViewModel.Environment.Interface.swift | 1 + .../Chat/ChatViewModel.Environment.Mock.swift | 3 +- .../CallVisualizer.Environment.Mock.swift | 3 +- .../VideoCallVIewModel.Environment.Mock.swift | 4 +- .../Mocks/VideoCallViewController.Mock.swift | 5 +-- .../VideoCall/VideoCallTests.swift | 31 +++++++++++++++ .../RootCoordinator.Environment.Failing.swift | 8 ++-- .../Glia.Environment.Failing.swift | 5 ++- .../Sources/CallViewControllerTests.swift | 3 -- .../Sources/CallViewModelTests.swift | 38 +++++++++++++++++++ .../ChatViewModel/ChatViewModelTests.swift | 33 ++++++++-------- GliaWidgetsTests/Sources/Glia/GliaTests.swift | 23 +++++------ .../ProximityManager.Failing.swift | 13 +++++++ GliaWidgetsTests/UIKitBased.Failing.swift | 8 ---- .../ChatViewModel.Environment.Failing.swift | 3 +- ...llViewControllerDynamicTypeFontTests.swift | 5 ++- .../VideoCallViewControllerLayoutTests.swift | 5 ++- ...ideoCallViewControllerVoiceOverTests.swift | 5 ++- 49 files changed, 226 insertions(+), 166 deletions(-) create mode 100644 GliaWidgets/Sources/ProximityManager/ProximityManager.Mock.swift create mode 100644 GliaWidgetsTests/Sources/ProximityManager/ProximityManager.Failing.swift diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index 0be365c56..067669301 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -349,6 +349,8 @@ 845E2F98283FC9A900C04D56 /* Theme.Survey.OptionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845E2F97283FC9A900C04D56 /* Theme.Survey.OptionButton.swift */; }; 845E2F9B283FCA9000C04D56 /* Theme.Survey.Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845E2F9A283FCA9000C04D56 /* Theme.Survey.Checkbox.swift */; }; 845E2F9D283FCB1400C04D56 /* Theme.Survey.Checkbox.Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845E2F9C283FCB1400C04D56 /* Theme.Survey.Checkbox.Accessibility.swift */; }; + 84602A742AE94DE50031E606 /* ProximityManager.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84602A732AE94DE50031E606 /* ProximityManager.Mock.swift */; }; + 84602A772AEA5BEA0031E606 /* ProximityManager.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84602A762AEA5BEA0031E606 /* ProximityManager.Failing.swift */; }; 8464297A2A44937600943BD6 /* AlertViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846429792A44937600943BD6 /* AlertViewControllerTests.swift */; }; 8464297D2A459E7D00943BD6 /* UIViewController+Replaceble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8464297C2A459E7D00943BD6 /* UIViewController+Replaceble.swift */; }; 846429802A45A1F200943BD6 /* OfferPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8464297F2A45A1F200943BD6 /* OfferPresenter.swift */; }; @@ -1089,6 +1091,8 @@ 845E2F97283FC9A900C04D56 /* Theme.Survey.OptionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.Survey.OptionButton.swift; sourceTree = ""; }; 845E2F9A283FCA9000C04D56 /* Theme.Survey.Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.Survey.Checkbox.swift; sourceTree = ""; }; 845E2F9C283FCB1400C04D56 /* Theme.Survey.Checkbox.Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.Survey.Checkbox.Accessibility.swift; sourceTree = ""; }; + 84602A732AE94DE50031E606 /* ProximityManager.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityManager.Mock.swift; sourceTree = ""; }; + 84602A762AEA5BEA0031E606 /* ProximityManager.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityManager.Failing.swift; sourceTree = ""; }; 846429792A44937600943BD6 /* AlertViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewControllerTests.swift; sourceTree = ""; }; 8464297C2A459E7D00943BD6 /* UIViewController+Replaceble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Replaceble.swift"; sourceTree = ""; }; 8464297F2A45A1F200943BD6 /* OfferPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfferPresenter.swift; sourceTree = ""; }; @@ -2670,6 +2674,7 @@ 7512A57827BF9FB800319DF1 /* Sources */ = { isa = PBXGroup; children = ( + 84602A752AEA5BDE0031E606 /* ProximityManager */, 84681A932A61840700DD7406 /* ChatViewModel */, 846429812A45DA5900943BD6 /* AlertViewController */, 846A5C4329F6BEB60049B29F /* Glia */, @@ -3117,6 +3122,14 @@ path = URLScheme; sourceTree = ""; }; + 84602A752AEA5BDE0031E606 /* ProximityManager */ = { + isa = PBXGroup; + children = ( + 84602A762AEA5BEA0031E606 /* ProximityManager.Failing.swift */, + ); + path = ProximityManager; + sourceTree = ""; + }; 8464297B2A459E3500943BD6 /* Replaceble */ = { isa = PBXGroup; children = ( @@ -3524,6 +3537,7 @@ isa = PBXGroup; children = ( C05E3EDD29C99E070013BC81 /* ProximityManager.swift */, + 84602A732AE94DE50031E606 /* ProximityManager.Mock.swift */, ); path = ProximityManager; sourceTree = ""; @@ -4631,6 +4645,7 @@ 755D187129A6A6400009F5E8 /* WelcomeStyle+MessageTextViewNormalStyle.swift in Sources */, 845A28FC28AFF092008558EA /* URLScheme.swift in Sources */, 75F58EE127E7D5300065BA2D /* Survey.ViewController.Props.swift in Sources */, + 84602A742AE94DE50031E606 /* ProximityManager.Mock.swift in Sources */, 754CC61527E27C42005676E9 /* Survey.Checkbox.swift in Sources */, 84681AA72A681EF900DD7406 /* QuickReplyButtonStyle.swift in Sources */, 1A4AD3CA256E864800468BFB /* ThemeColorStyle.swift in Sources */, @@ -4857,6 +4872,7 @@ 8491AF502A9CBB0400CC3E72 /* TranscriptModelTests+GVA.swift in Sources */, C03A8049292BC8DB00DDECA6 /* CallViewControllerTests.swift in Sources */, 84D5B9662A15204400807F92 /* QuickLookBased.Failing.swift in Sources */, + 84602A772AEA5BEA0031E606 /* ProximityManager.Failing.swift in Sources */, 84681A982A61853300DD7406 /* GvaOption.Mock.swift in Sources */, AF6AB34B2989517100003645 /* FileUploader.Failing.swift in Sources */, EB03B00E27FFF6DD0058F6B1 /* CallViewTests.swift in Sources */, diff --git a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift index d2bbc12ae..616d4581c 100644 --- a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift +++ b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift @@ -137,7 +137,6 @@ extension Glia { submitSurveyAnswer: environment.coreSdk.submitSurveyAnswer, uiApplication: environment.uiApplication, uiScreen: environment.uiScreen, - uiDevice: environment.uiDevice, notificationCenter: environment.notificationCenter, fetchChatHistory: environment.coreSdk.fetchChatHistory, listQueues: environment.coreSdk.listQueues, @@ -161,7 +160,8 @@ extension Glia { stopSocketObservation: environment.coreSdk.stopSocketObservation, pushNotifications: environment.coreSdk.pushNotifications, createSendMessagePayload: environment.coreSdk.createSendMessagePayload, - orientationManager: environment.orientationManager + orientationManager: environment.orientationManager, + proximityManager: environment.proximityManager ) ) rootCoordinator?.delegate = { [weak self] event in diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index 7b2658f3e..b6249ada2 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -84,7 +84,8 @@ public class Glia { assetsBuilder: { [weak self] in self?.assetsBuilder ?? .standard }, getCurrentEngagement: environment.coreSdk.getCurrentEngagement, eventHandler: onEvent, - orientationManager: environment.orientationManager + orientationManager: environment.orientationManager, + proximityManager: environment.proximityManager ) ) var rootCoordinator: EngagementCoordinator? diff --git a/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift b/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift index 728c494c9..a9f71e546 100644 --- a/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift +++ b/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift @@ -281,7 +281,8 @@ extension SecureConversations.Coordinator { interactor: environment.interactor, startSocketObservation: environment.startSocketObservation, stopSocketObservation: environment.stopSocketObservation, - createSendMessagePayload: environment.createSendMessagePayload + createSendMessagePayload: environment.createSendMessagePayload, + proximityManager: environment.proximityManager ), startWithSecureTranscriptFlow: true ) @@ -321,7 +322,6 @@ extension SecureConversations.Coordinator { var uuid: () -> UUID var uiApplication: UIKitBased.UIApplication var uiScreen: UIKitBased.UIScreen - var uiDevice: UIKitBased.UIDevice var notificationCenter: FoundationBased.NotificationCenter var createFileUploadListModel: SecureConversations.FileUploadListViewModel.Create var viewFactory: ViewFactory @@ -348,6 +348,7 @@ extension SecureConversations.Coordinator { var stopSocketObservation: CoreSdkClient.StopSocketObservation var createSendMessagePayload: CoreSdkClient.CreateSendMessagePayload var orientationManager: OrientationManager + var proximityManager: ProximityManager } enum DelegateEvent { diff --git a/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Environment.swift b/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Environment.swift index 79f1ecdde..03d204825 100644 --- a/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Environment.swift +++ b/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Environment.swift @@ -24,5 +24,6 @@ extension CallVisualizer { var getCurrentEngagement: CoreSdkClient.GetCurrentEngagement var eventHandler: ((GliaEvent) -> Void)? var orientationManager: OrientationManager + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift b/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift index baa971a1c..bd9893135 100644 --- a/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift +++ b/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift @@ -59,7 +59,8 @@ public final class CallVisualizer { self?.environment.eventHandler?(.maximized) } }, - orientationManager: environment.orientationManager + orientationManager: environment.orientationManager, + proximityManager: environment.proximityManager ) ) }() diff --git a/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.Environment.swift b/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.Environment.swift index cfc07aa4c..fd7ec601c 100644 --- a/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.Environment.swift +++ b/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.Environment.swift @@ -21,5 +21,6 @@ extension CallVisualizer.Coordinator { var engagedOperator: () -> CoreSdkClient.Operator? var eventHandler: (DelegateEvent) -> Void var orientationManager: OrientationManager + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.swift b/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.swift index 302b708ae..5bcc9220b 100644 --- a/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.swift +++ b/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.swift @@ -214,7 +214,8 @@ extension CallVisualizer { notificationCenter: environment.notificationCenter, date: environment.date, engagedOperator: environment.engagedOperator, - screenShareHandler: environment.screenShareHandler + screenShareHandler: environment.screenShareHandler, + proximityManager: environment.proximityManager ), theme: environment.viewFactory.theme, call: .init( diff --git a/GliaWidgets/Sources/CallVisualizer/VideoCall/Coordinator/VideoCallCoordinator.Environment.swift b/GliaWidgets/Sources/CallVisualizer/VideoCall/Coordinator/VideoCallCoordinator.Environment.swift index 43d0a5961..3154af522 100644 --- a/GliaWidgets/Sources/CallVisualizer/VideoCall/Coordinator/VideoCallCoordinator.Environment.swift +++ b/GliaWidgets/Sources/CallVisualizer/VideoCall/Coordinator/VideoCallCoordinator.Environment.swift @@ -14,5 +14,6 @@ extension CallVisualizer.VideoCallCoordinator { var date: () -> Date var engagedOperator: () -> CoreSdkClient.Operator? var screenShareHandler: ScreenShareHandler + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/CallVisualizer/VideoCall/Coordinator/VideoCallCoordinator.swift b/GliaWidgets/Sources/CallVisualizer/VideoCall/Coordinator/VideoCallCoordinator.swift index 1243386c2..e9962721c 100644 --- a/GliaWidgets/Sources/CallVisualizer/VideoCall/Coordinator/VideoCallCoordinator.swift +++ b/GliaWidgets/Sources/CallVisualizer/VideoCall/Coordinator/VideoCallCoordinator.swift @@ -44,7 +44,8 @@ extension CallVisualizer { notificationCenter: environment.notificationCenter, date: environment.date, engagedOperator: environment.engagedOperator, - screenShareHandler: environment.screenShareHandler + screenShareHandler: environment.screenShareHandler, + proximityManager: environment.proximityManager ), call: call ) @@ -57,9 +58,6 @@ extension CallVisualizer { gcd: environment.gcd, uiScreen: environment.uiScreen ), - uiApplication: environment.uiApplication, - uiScreen: environment.uiScreen, - uiDevice: environment.uiDevice, notificationCenter: environment.notificationCenter ) ) diff --git a/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewController/VideoCallViewController.swift b/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewController/VideoCallViewController.swift index a64e56643..212e644a5 100644 --- a/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewController/VideoCallViewController.swift +++ b/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewController/VideoCallViewController.swift @@ -4,7 +4,6 @@ extension CallVisualizer { final class VideoCallViewController: UIViewController { private let videoCallView: VideoCallView private let environment: Environment - private var proximityManager: ProximityManager? var props: Props { didSet { @@ -24,10 +23,6 @@ extension CallVisualizer { super.init(nibName: nil, bundle: nil) } - deinit { - proximityManager?.stop() - } - // MARK: - Required required init?(coder: NSCoder) { @@ -42,16 +37,7 @@ extension CallVisualizer { override func viewDidLoad() { super.viewDidLoad() - proximityManager = .init( - view: self.view, - environment: .init( - uiApplication: environment.uiApplication, - uiDevice: environment.uiDevice, - uiScreen: environment.uiScreen, - notificationCenter: environment.notificationCenter - ) - ) - proximityManager?.start() + props.viewDidLoad() } } } @@ -61,6 +47,7 @@ extension CallVisualizer { extension CallVisualizer.VideoCallViewController { struct Props: Equatable { let videoCallViewProps: CallVisualizer.VideoCallView.Props + let viewDidLoad: Cmd } } @@ -69,9 +56,6 @@ extension CallVisualizer.VideoCallViewController { extension CallVisualizer.VideoCallViewController { struct Environment { var videoCallView: CallVisualizer.VideoCallView.Environment - var uiApplication: UIKitBased.UIApplication - var uiScreen: UIKitBased.UIScreen - var uiDevice: UIKitBased.UIDevice var notificationCenter: FoundationBased.NotificationCenter } } diff --git a/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.Environment.swift b/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.Environment.swift index ca8b04957..10bf32735 100644 --- a/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.Environment.swift +++ b/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.Environment.swift @@ -12,5 +12,6 @@ extension CallVisualizer.VideoCallViewModel { var date: () -> Date var engagedOperator: () -> CoreSdkClient.Operator? var screenShareHandler: ScreenShareHandler + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.swift b/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.swift index 036c104de..f65a093f8 100644 --- a/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.swift +++ b/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.swift @@ -130,6 +130,10 @@ extension CallVisualizer { object: nil ) } + + deinit { + environment.proximityManager.stop() + } } } @@ -180,7 +184,10 @@ extension CallVisualizer.VideoCallViewModel { ), style: style.header ) - ) + ), + viewDidLoad: .init { [weak self] in + self?.environment.proximityManager.start() + } ) } diff --git a/GliaWidgets/Sources/Coordinators/Call/CallCoordinator.swift b/GliaWidgets/Sources/Coordinators/Call/CallCoordinator.swift index 8f52d67d8..ce3ae2512 100644 --- a/GliaWidgets/Sources/Coordinators/Call/CallCoordinator.swift +++ b/GliaWidgets/Sources/Coordinators/Call/CallCoordinator.swift @@ -81,7 +81,8 @@ private extension CallCoordinator { fetchChatHistory: environment.fetchChatHistory, fileUploadListStyle: viewFactory.theme.chatStyle.messageEntry.uploadList, createFileUploadListModel: environment.createFileUploadListModel, - createSendMessagePayload: environment.createSendMessagePayload + createSendMessagePayload: environment.createSendMessagePayload, + proximityManager: environment.proximityManager ), call: call, unreadMessages: unreadMessages, @@ -97,9 +98,6 @@ private extension CallCoordinator { viewModel: viewModel, viewFactory: viewFactory, environment: .init( - uiApplication: environment.uiApplication, - uiScreen: environment.uiScreen, - uiDevice: environment.uiDevice, notificationCenter: environment.notificationCenter ) ) @@ -147,10 +145,10 @@ extension CallCoordinator { var uuid: () -> UUID var uiApplication: UIKitBased.UIApplication var uiScreen: UIKitBased.UIScreen - var uiDevice: UIKitBased.UIDevice var notificationCenter: FoundationBased.NotificationCenter var fetchChatHistory: CoreSdkClient.FetchChatHistory var createFileUploadListModel: SecureConversations.FileUploadListViewModel.Create var createSendMessagePayload: CoreSdkClient.CreateSendMessagePayload + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.Environment.swift b/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.Environment.swift index 6722c071a..f2180f8a9 100644 --- a/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.Environment.swift +++ b/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.Environment.swift @@ -32,5 +32,6 @@ extension ChatCoordinator { var startSocketObservation: CoreSdkClient.StartSocketObservation var stopSocketObservation: CoreSdkClient.StopSocketObservation var createSendMessagePayload: CoreSdkClient.CreateSendMessagePayload + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.swift b/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.swift index 11f08d5dd..1dc4d1170 100644 --- a/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.swift +++ b/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.swift @@ -220,7 +220,8 @@ extension ChatCoordinator { fetchChatHistory: environment.fetchChatHistory, fileUploadListStyle: viewFactory.theme.chatStyle.messageEntry.uploadList, createFileUploadListModel: environment.createFileUploadListModel, - createSendMessagePayload: environment.createSendMessagePayload + createSendMessagePayload: environment.createSendMessagePayload, + proximityManager: environment.proximityManager ) } } diff --git a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.Mock.swift b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.Mock.swift index 0c297c735..a2c903b5d 100644 --- a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.Mock.swift +++ b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.Mock.swift @@ -18,7 +18,6 @@ extension EngagementCoordinator.Environment { submitSurveyAnswer: { _, _, _, _ in }, uiApplication: .mock, uiScreen: .mock, - uiDevice: .mock, notificationCenter: .mock, fetchChatHistory: { _ in }, listQueues: { _ in }, @@ -35,7 +34,8 @@ extension EngagementCoordinator.Environment { stopSocketObservation: {}, pushNotifications: .mock, createSendMessagePayload: { _, _ in .mock() }, - orientationManager: .mock() + orientationManager: .mock(), + proximityManager: .mock ) } #endif diff --git a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.swift b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.swift index e59d803ed..f8367e3af 100644 --- a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.swift +++ b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.swift @@ -19,7 +19,6 @@ extension EngagementCoordinator { var submitSurveyAnswer: CoreSdkClient.SubmitSurveyAnswer var uiApplication: UIKitBased.UIApplication var uiScreen: UIKitBased.UIScreen - var uiDevice: UIKitBased.UIDevice var notificationCenter: FoundationBased.NotificationCenter var fetchChatHistory: CoreSdkClient.FetchChatHistory var listQueues: CoreSdkClient.ListQueues @@ -37,5 +36,6 @@ extension EngagementCoordinator { var pushNotifications: CoreSdkClient.PushNotifications var createSendMessagePayload: CoreSdkClient.CreateSendMessagePayload var orientationManager: OrientationManager + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift index e9c268e8f..80a782456 100644 --- a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift +++ b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift @@ -255,7 +255,8 @@ extension EngagementCoordinator { interactor: interactor, startSocketObservation: environment.startSocketObservation, stopSocketObservation: environment.stopSocketObservation, - createSendMessagePayload: environment.createSendMessagePayload + createSendMessagePayload: environment.createSendMessagePayload, + proximityManager: environment.proximityManager ), startWithSecureTranscriptFlow: false ) @@ -343,11 +344,11 @@ extension EngagementCoordinator { uuid: environment.uuid, uiApplication: environment.uiApplication, uiScreen: environment.uiScreen, - uiDevice: environment.uiDevice, notificationCenter: environment.notificationCenter, fetchChatHistory: environment.fetchChatHistory, createFileUploadListModel: environment.createFileUploadListModel, - createSendMessagePayload: environment.createSendMessagePayload + createSendMessagePayload: environment.createSendMessagePayload, + proximityManager: environment.proximityManager ) ) coordinator.delegate = { [weak self] event in @@ -453,7 +454,6 @@ extension EngagementCoordinator { uuid: environment.uuid, uiApplication: environment.uiApplication, uiScreen: environment.uiScreen, - uiDevice: environment.uiDevice, notificationCenter: environment.notificationCenter, createFileUploadListModel: environment.createFileUploadListModel, viewFactory: viewFactory, @@ -479,7 +479,8 @@ extension EngagementCoordinator { startSocketObservation: environment.startSocketObservation, stopSocketObservation: environment.stopSocketObservation, createSendMessagePayload: environment.createSendMessagePayload, - orientationManager: environment.orientationManager + orientationManager: environment.orientationManager, + proximityManager: environment.proximityManager ) ) diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift index cc7a72360..acf794415 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift @@ -46,6 +46,7 @@ extension Glia { var messagesWithUnreadCountLoaderScheduler: CoreSdkClient.ReactiveSwift.DateScheduler var orientationManager: OrientationManager var coreSDKConfigurator: CoreSDKConfigurator + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift index 42dff4046..e86916ed4 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift @@ -33,7 +33,11 @@ extension Glia.Environment { uiDevice: .live, notificationCenter: .live )), - coreSDKConfigurator: .create(coreSdk: .live) + coreSDKConfigurator: .create(coreSdk: .live), + proximityManager: .init(environment: .init( + uiApplication: .live, + uiDevice: .live + )) ) } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift index fb5213a2c..3f282149d 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift @@ -27,7 +27,8 @@ extension Glia.Environment { screenShareHandler: .mock, messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, orientationManager: .mock(), - coreSDKConfigurator: .mock + coreSDKConfigurator: .mock, + proximityManager: .mock ) } diff --git a/GliaWidgets/Sources/ProximityManager/ProximityManager.Mock.swift b/GliaWidgets/Sources/ProximityManager/ProximityManager.Mock.swift new file mode 100644 index 000000000..7a8f6940d --- /dev/null +++ b/GliaWidgets/Sources/ProximityManager/ProximityManager.Mock.swift @@ -0,0 +1,12 @@ +#if DEBUG +import Foundation + +extension ProximityManager { + static let mock: ProximityManager = .init( + environment: .init( + uiApplication: .mock, + uiDevice: .mock + ) + ) +} +#endif diff --git a/GliaWidgets/Sources/ProximityManager/ProximityManager.swift b/GliaWidgets/Sources/ProximityManager/ProximityManager.swift index f364e18f0..3571ff5e9 100644 --- a/GliaWidgets/Sources/ProximityManager/ProximityManager.swift +++ b/GliaWidgets/Sources/ProximityManager/ProximityManager.swift @@ -1,50 +1,20 @@ import UIKit -class ProximityManager { - private var userDefaultScreenBrightness: CGFloat +final class ProximityManager { private let environment: Environment - private let view: UIView - init( - view: UIView, - environment: Environment - ) { - self.view = view + init(environment: Environment) { self.environment = environment - self.userDefaultScreenBrightness = environment.uiScreen.brightness() } func start() { environment.uiApplication.isIdleTimerDisabled(true) environment.uiDevice.isProximityMonitoringEnabled(true) - environment.notificationCenter.addObserver( - self, - selector: #selector(proximityStateDidChange), - name: UIDevice.proximityStateDidChangeNotification, - object: nil - ) } func stop() { - environment.notificationCenter.removeObserver( - self, - name: UIDevice.proximityStateDidChangeNotification, - object: nil - ) environment.uiApplication.isIdleTimerDisabled(false) - view.isUserInteractionEnabled = true - } -} - -private extension ProximityManager { - @objc func proximityStateDidChange() { - if environment.uiDevice.proximityState() { - environment.uiScreen.setBrightness(0.0) - view.isUserInteractionEnabled = false - } else { - environment.uiScreen.setBrightness(userDefaultScreenBrightness) - view.isUserInteractionEnabled = true - } + environment.uiDevice.isProximityMonitoringEnabled(false) } } @@ -52,7 +22,5 @@ extension ProximityManager { struct Environment { var uiApplication: UIKitBased.UIApplication var uiDevice: UIKitBased.UIDevice - var uiScreen: UIKitBased.UIScreen - var notificationCenter: FoundationBased.NotificationCenter } } diff --git a/GliaWidgets/Sources/UIKitBased/UIKitBased.Interface.swift b/GliaWidgets/Sources/UIKitBased/UIKitBased.Interface.swift index c1f8a3ab4..d8031661d 100644 --- a/GliaWidgets/Sources/UIKitBased/UIKitBased.Interface.swift +++ b/GliaWidgets/Sources/UIKitBased/UIKitBased.Interface.swift @@ -21,8 +21,6 @@ enum UIKitBased { } struct UIScreen { - var brightness: () -> CGFloat - var setBrightness: (CGFloat) -> Void var bounds: () -> CGRect var scale: () -> CGFloat } diff --git a/GliaWidgets/Sources/UIKitBased/UIKitBased.Live.swift b/GliaWidgets/Sources/UIKitBased/UIKitBased.Live.swift index 7527cf763..7f8cfb11b 100644 --- a/GliaWidgets/Sources/UIKitBased/UIKitBased.Live.swift +++ b/GliaWidgets/Sources/UIKitBased/UIKitBased.Live.swift @@ -25,8 +25,6 @@ extension UIKitBased.UIDevice { extension UIKitBased.UIScreen { static let live = Self.init( - brightness: { UIScreen.main.brightness }, - setBrightness: { UIScreen.main.brightness = $0 }, bounds: { UIScreen.main.bounds }, scale: { UIScreen.main.scale } ) diff --git a/GliaWidgets/Sources/UIKitBased/UIKitBased.Mock.swift b/GliaWidgets/Sources/UIKitBased/UIKitBased.Mock.swift index 3c0eb769d..2079db6aa 100644 --- a/GliaWidgets/Sources/UIKitBased/UIKitBased.Mock.swift +++ b/GliaWidgets/Sources/UIKitBased/UIKitBased.Mock.swift @@ -36,8 +36,6 @@ extension UIKitBased.UIDevice { extension UIKitBased.UIScreen { static let mock = Self.init( - brightness: { .init() }, - setBrightness: { _ in }, bounds: { UIScreen.main.bounds }, scale: { .init() } ) diff --git a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift index 8e5d6d78b..3a0983729 100644 --- a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift @@ -10,9 +10,6 @@ extension CallViewController { viewModel: viewModel, viewFactory: viewFactory, environment: .init( - uiApplication: .mock, - uiScreen: .mock, - uiDevice: .mock, notificationCenter: .mock ) ) diff --git a/GliaWidgets/Sources/ViewController/Call/CallViewController.swift b/GliaWidgets/Sources/ViewController/Call/CallViewController.swift index ca17b1d58..013b8a3bc 100644 --- a/GliaWidgets/Sources/ViewController/Call/CallViewController.swift +++ b/GliaWidgets/Sources/ViewController/Call/CallViewController.swift @@ -3,7 +3,6 @@ import UIKit final class CallViewController: EngagementViewController { private let viewModel: CallViewModel private let environment: Environment - private var proximityManager: ProximityManager? init(viewModel: CallViewModel, viewFactory: ViewFactory, environment: Environment) { self.environment = environment @@ -13,7 +12,6 @@ final class CallViewController: EngagementViewController { deinit { environment.notificationCenter.removeObserver(self) - proximityManager?.stop() } override public func loadView() { @@ -30,17 +28,7 @@ final class CallViewController: EngagementViewController { override func viewDidLoad() { super.viewDidLoad() - proximityManager = .init( - view: self.view, - environment: .init( - uiApplication: environment.uiApplication, - uiDevice: environment.uiDevice, - uiScreen: environment.uiScreen, - notificationCenter: environment.notificationCenter - ) - ) viewModel.event(.viewDidLoad) - proximityManager?.start() environment.notificationCenter.addObserver( self, @@ -189,9 +177,6 @@ private extension CallButton.State { extension CallViewController { struct Environment { - var uiApplication: UIKitBased.UIApplication - var uiScreen: UIKitBased.UIScreen - var uiDevice: UIKitBased.UIDevice var notificationCenter: FoundationBased.NotificationCenter } } diff --git a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift index 5b1c85064..a4fb31855 100644 --- a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift @@ -63,7 +63,6 @@ extension ChatViewController { let messageId = { messageUuid().uuidString } let fileUuid = UUID.incrementing let fileId = { fileUuid().uuidString } - let queueId = UUID.mock.uuidString let operatorAttachmentURL = URL.mock.appendingPathComponent("image").appendingPathExtension("png") let messages: [ChatMessage] = [ @@ -320,7 +319,6 @@ extension ChatViewController { chatViewModelEnv.loadChatMessagesFromHistory = { true } let messageUuid = UUID.incrementing let messageId = { messageUuid().uuidString } - let queueId = UUID.mock.uuidString let options = [ ChatChoiceCardOption(with: try .mock(text: "Four", value: "ruof")), @@ -373,7 +371,6 @@ extension ChatViewController { let messageUuid = UUID.incrementing let messageId = { messageUuid().uuidString } - let queueId = UUID.mock.uuidString let jsonData = mockGvaPersistentButtonJson() ?? Data() let metadataContainer = try CoreSdkMessageMetadataContainer(jsonData: jsonData, jsonDecoder: .init()) @@ -420,7 +417,6 @@ extension ChatViewController { let messageUuid = UUID.incrementing let messageId = { messageUuid().uuidString } - let queueId = UUID.mock.uuidString let jsonData = mockGvaResponseTextJson() ?? Data() let metadataContainer = try CoreSdkMessageMetadataContainer(jsonData: jsonData, jsonDecoder: .init()) @@ -467,7 +463,6 @@ extension ChatViewController { let messageUuid = UUID.incrementing let messageId = { messageUuid().uuidString } - let queueId = UUID.mock.uuidString let jsonData = mockGvaGalleryCardJson() ?? Data() let metadataContainer = try CoreSdkMessageMetadataContainer(jsonData: jsonData, jsonDecoder: .init()) diff --git a/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift b/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift index 86a705cb5..5d194a066 100644 --- a/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift @@ -59,6 +59,10 @@ class CallViewModel: EngagementViewModel, ViewModel { } } + deinit { + environment.proximityManager.stop() + } + func event(_ event: Event) { switch event { case .viewDidLoad: @@ -71,6 +75,7 @@ class CallViewModel: EngagementViewModel, ViewModel { override func start() { super.start() + environment.proximityManager.start() update(for: call.kind.value) // In the case when SDK is configured once and then diff --git a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.Environment.Interface.swift b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.Environment.Interface.swift index 13feedf10..0f6d8fc2b 100644 --- a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.Environment.Interface.swift +++ b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.Environment.Interface.swift @@ -22,5 +22,6 @@ extension EngagementViewModel { var fileUploadListStyle: FileUploadListStyle var createFileUploadListModel: SecureConversations.FileUploadListViewModel.Create var createSendMessagePayload: CoreSdkClient.CreateSendMessagePayload + var proximityManager: ProximityManager } } diff --git a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.Environment.Mock.swift b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.Environment.Mock.swift index f0596786a..1906cd179 100644 --- a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.Environment.Mock.swift +++ b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.Environment.Mock.swift @@ -29,7 +29,8 @@ extension ChatViewModel.Environment { fetchChatHistory: { _ in }, fileUploadListStyle: .initial, createFileUploadListModel: SecureConversations.FileUploadListViewModel.mock(environment:), - createSendMessagePayload: { _, _ in .mock() } + createSendMessagePayload: { _, _ in .mock() }, + proximityManager: .mock ) } #endif diff --git a/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/CallVisualizer.Environment.Mock.swift b/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/CallVisualizer.Environment.Mock.swift index be3732d4d..f6c8b3632 100644 --- a/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/CallVisualizer.Environment.Mock.swift +++ b/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/CallVisualizer.Environment.Mock.swift @@ -22,7 +22,8 @@ extension CallVisualizer.Environment { uiConfig: { nil }, assetsBuilder: { .standard }, getCurrentEngagement: CoreSdkClient.mock.getCurrentEngagement, - orientationManager: .mock() + orientationManager: .mock(), + proximityManager: .mock ) } diff --git a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/VideoCallVIewModel.Environment.Mock.swift b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/VideoCallVIewModel.Environment.Mock.swift index 37e517f01..bf4437f9c 100644 --- a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/VideoCallVIewModel.Environment.Mock.swift +++ b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/VideoCallVIewModel.Environment.Mock.swift @@ -1,4 +1,3 @@ - #if DEBUG extension CallVisualizer.VideoCallViewModel.Environment { @@ -12,7 +11,8 @@ extension CallVisualizer.VideoCallViewModel.Environment { notificationCenter: .mock, date: { .mock }, engagedOperator: { .mock() }, - screenShareHandler: .mock + screenShareHandler: .mock, + proximityManager: .mock ) } diff --git a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/VideoCallViewController.Mock.swift b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/VideoCallViewController.Mock.swift index 818e36744..bb7bcf65e 100644 --- a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/VideoCallViewController.Mock.swift +++ b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/VideoCallViewController.Mock.swift @@ -2,12 +2,9 @@ extension CallVisualizer.VideoCallViewController { static func mock( - props: Props = .init(videoCallViewProps: .mock()), + props: Props = .init(videoCallViewProps: .mock(), viewDidLoad: .nop), environment: CallVisualizer.VideoCallViewController.Environment = .init( videoCallView: .mock, - uiApplication: .mock, - uiScreen: .mock, - uiDevice: .mock, notificationCenter: .mock ) ) -> CallVisualizer.VideoCallViewController { diff --git a/GliaWidgetsTests/CallVisualizer/VideoCall/VideoCallTests.swift b/GliaWidgetsTests/CallVisualizer/VideoCall/VideoCallTests.swift index f634736be..ea799e479 100644 --- a/GliaWidgetsTests/CallVisualizer/VideoCall/VideoCallTests.swift +++ b/GliaWidgetsTests/CallVisualizer/VideoCall/VideoCallTests.swift @@ -80,6 +80,37 @@ final class VideoCallTests: XCTestCase { XCTAssertTrue(props.videoCallViewProps.endScreenShareButtonHidden) } + + func test_proximityManagerStartsAndStops() { + enum Call: Equatable { case isIdleTimerDisabled(Bool), isProximityMonitoringEnabled(Bool) } + var calls: [Call] = [] + var env = CallVisualizer.VideoCallViewModel.Environment.mock + var proximityManagerEnv = ProximityManager.Environment.failing + proximityManagerEnv.uiApplication.isIdleTimerDisabled = { value in + calls.append(.isIdleTimerDisabled(value)) + } + proximityManagerEnv.uiDevice.isProximityMonitoringEnabled = { value in + calls.append(.isProximityMonitoringEnabled(value)) + } + env.proximityManager = .init(environment: proximityManagerEnv) + var viewModel: CallVisualizer.VideoCallViewModel? = .mock(environment: env) + let props = viewModel?.makeProps() + + props?.viewDidLoad() + + XCTAssertEqual(calls, [ + .isIdleTimerDisabled(true), + .isProximityMonitoringEnabled(true) + ]) + + viewModel = nil + XCTAssertEqual(calls, [ + .isIdleTimerDisabled(true), + .isProximityMonitoringEnabled(true), + .isIdleTimerDisabled(false), + .isProximityMonitoringEnabled(false) + ]) + } } private extension VideoCallTests { diff --git a/GliaWidgetsTests/Coordinator/RootCoordinator.Environment.Failing.swift b/GliaWidgetsTests/Coordinator/RootCoordinator.Environment.Failing.swift index 366c4165a..7e24ad575 100644 --- a/GliaWidgetsTests/Coordinator/RootCoordinator.Environment.Failing.swift +++ b/GliaWidgetsTests/Coordinator/RootCoordinator.Environment.Failing.swift @@ -39,9 +39,8 @@ extension EngagementCoordinator.Environment { }, uiApplication: .failing, uiScreen: .failing, - uiDevice: .failing, notificationCenter: .failing, - fetchChatHistory: { _ in fail("\(Self.self).fetchChatHistory")}, + fetchChatHistory: { _ in fail("\(Self.self).fetchChatHistory") }, listQueues: { _ in fail("\(Self.self).listQueues") }, sendSecureMessagePayload: { _, _, _ in fail("\(Self.self).sendSecureMessagePayload") @@ -85,7 +84,8 @@ extension EngagementCoordinator.Environment { createSendMessagePayload: { _, _ in fail("\(Self.self).createSendMessagePayload") return .mock() - }, - orientationManager: .mock() + }, + orientationManager: .mock(), + proximityManager: .failing ) } diff --git a/GliaWidgetsTests/Glia.Environment.Failing.swift b/GliaWidgetsTests/Glia.Environment.Failing.swift index dc85373dd..1b7fa9187 100644 --- a/GliaWidgetsTests/Glia.Environment.Failing.swift +++ b/GliaWidgetsTests/Glia.Environment.Failing.swift @@ -54,9 +54,10 @@ extension Glia.Environment { return .mock() }, screenShareHandler: .mock, - messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, + messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, orientationManager: .mock(), - coreSDKConfigurator: .failing + coreSDKConfigurator: .failing, + proximityManager: .failing ) } diff --git a/GliaWidgetsTests/Sources/CallViewControllerTests.swift b/GliaWidgetsTests/Sources/CallViewControllerTests.swift index 17ff286f6..d78e18b25 100644 --- a/GliaWidgetsTests/Sources/CallViewControllerTests.swift +++ b/GliaWidgetsTests/Sources/CallViewControllerTests.swift @@ -10,9 +10,6 @@ class CallViewControllerTests: XCTestCase { viewModel: CallViewModel.mock(environment: CallViewModel.Environment.mock), viewFactory: ViewFactory.mock(), environment: .init( - uiApplication: .mock, - uiScreen: .mock, - uiDevice: .mock, notificationCenter: .mock ) ) diff --git a/GliaWidgetsTests/Sources/CallViewModelTests.swift b/GliaWidgetsTests/Sources/CallViewModelTests.swift index 68b97d7e4..492b4c423 100644 --- a/GliaWidgetsTests/Sources/CallViewModelTests.swift +++ b/GliaWidgetsTests/Sources/CallViewModelTests.swift @@ -347,4 +347,42 @@ class CallViewModelTests: XCTestCase { XCTAssertEqual(call.state.value, .none) } + + func test_proximityManagerStartsAndStops() { + enum Call: Equatable { case isIdleTimerDisabled(Bool), isProximityMonitoringEnabled(Bool) } + var calls: [Call] = [] + var env = CallViewModel.Environment.failing() + var proximityManagerEnv = ProximityManager.Environment.failing + proximityManagerEnv.uiApplication.isIdleTimerDisabled = { value in + calls.append(.isIdleTimerDisabled(value)) + } + proximityManagerEnv.uiDevice.isProximityMonitoringEnabled = { value in + calls.append(.isProximityMonitoringEnabled(value)) + } + env.proximityManager = .init(environment: proximityManagerEnv) + viewModel = .init( + interactor: .mock(), + alertConfiguration: .mock(), + screenShareHandler: .mock, + environment: env, + call: .mock(), + unreadMessages: .init(with: 0), + startWith: .engagement(mediaType: .video) + ) + + viewModel.event(.viewDidLoad) + + XCTAssertEqual(calls, [ + .isIdleTimerDisabled(true), + .isProximityMonitoringEnabled(true) + ]) + + viewModel = nil + XCTAssertEqual(calls, [ + .isIdleTimerDisabled(true), + .isProximityMonitoringEnabled(true), + .isIdleTimerDisabled(false), + .isProximityMonitoringEnabled(false) + ]) + } } diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift index 0f3675862..fd7479211 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift @@ -61,7 +61,8 @@ class ChatViewModelTests: XCTestCase { calls.append(.sendSelectedOptionValue) } return .mock() - } + }, + proximityManager: .mock ) ) @@ -111,7 +112,7 @@ class ChatViewModelTests: XCTestCase { ) XCTAssertEqual(chatType, .nonAuthenticated) } - + func test_onInteractorStateEngagedClearsChatQueueSection() throws { var viewModelEnv = ChatViewModel.Environment.failing() viewModelEnv.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] } @@ -129,7 +130,7 @@ class ChatViewModelTests: XCTestCase { viewModel.update(for: .engaged(mockOperator)) XCTAssertEqual(0, viewModel.numberOfItems(in: queueSectionIndex)) } - + func test_onEngagementTransferringAddsTransferringItemToTheEndOfChat() throws { var interactorEnv: Interactor.Environment = .failing interactorEnv.gcd.mainQueue.asyncIfNeeded = { $0() } @@ -139,10 +140,10 @@ class ChatViewModelTests: XCTestCase { viewModelEnv.fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in } viewModelEnv.loadChatMessagesFromHistory = { true } viewModelEnv.fetchSiteConfigurations = { _ in } - + let interactor: Interactor = .mock(environment: interactorEnv) let viewModel: ChatViewModel = .mock(interactor: interactor, environment: viewModelEnv) - + interactor.onEngagementTransferring() let lastSectionIndex = viewModel.numberOfSections - 1 @@ -151,7 +152,7 @@ class ChatViewModelTests: XCTestCase { XCTAssertEqual(lastItemKind, .transferring) } - + func test_onEngagementTransferRemovesTransferringItemFromChat() throws { var interactorEnv: Interactor.Environment = .failing interactorEnv.gcd.mainQueue.asyncIfNeeded = { $0() } @@ -164,7 +165,7 @@ class ChatViewModelTests: XCTestCase { let interactor: Interactor = .mock(environment: interactorEnv) let viewModel: ChatViewModel = .mock(interactor: interactor, environment: viewModelEnv) - + interactor.onEngagementTransferring() let lastSectionIndex = viewModel.numberOfSections - 1 @@ -178,7 +179,7 @@ class ChatViewModelTests: XCTestCase { XCTAssertFalse(viewModel.messagesSection.items.contains(where: { $0.kind == .transferring })) } - + func test_onEngagementTransferAddsOperatorConnectedChatItemToTheEndOfChat() throws { var interactorEnv: Interactor.Environment = .failing interactorEnv.gcd.mainQueue.asyncIfNeeded = { $0() } @@ -258,7 +259,7 @@ class ChatViewModelTests: XCTestCase { // Then XCTAssertEqual(calls, [.fetchSiteConfigurations]) } - + func test_handleUrlWithPhoneOpensURLWithUIApplication() throws { enum Call: Equatable { case openUrl(URL) } @@ -278,7 +279,7 @@ class ChatViewModelTests: XCTestCase { XCTAssertEqual(calls, [.openUrl(telUrl)]) } - + func test_handleUrlWithEmailOpensURLWithUIApplication() throws { enum Call: Equatable { case openUrl(URL) } @@ -293,12 +294,12 @@ class ChatViewModelTests: XCTestCase { viewModelEnv.createFileUploadListModel = { _ in .mock() } let viewModel: ChatViewModel = .mock(interactor: .mock(), environment: viewModelEnv) - let mailUrl = URL(string: "mailto:mock@mock.mock")! + let mailUrl = try XCTUnwrap(URL(string: "mailto:mock@mock.mock")) viewModel.linkTapped(mailUrl) XCTAssertEqual(calls, [.openUrl(mailUrl)]) } - + func test_handleUrlWithLinkOpensCalsLinkTapped() throws { enum Call: Equatable { case canOpen(URL), open(URL) } var calls: [Call] = [] @@ -318,7 +319,7 @@ class ChatViewModelTests: XCTestCase { environment: viewModelEnv ) - let linkUrl = URL(string: "https://mock.mock")! + let linkUrl = try XCTUnwrap(URL(string: "https://mock.mock")) viewModel.linkTapped(linkUrl) XCTAssertEqual(calls, [.canOpen(linkUrl), .open(linkUrl)]) @@ -326,7 +327,7 @@ class ChatViewModelTests: XCTestCase { func test_handleUrlWithRandomScheme() throws { enum Call: Equatable { case canOpen(URL), open(URL) } - + var calls: [Call] = [] var viewModelEnv = ChatViewModel.Environment.failing() viewModelEnv.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] } @@ -340,7 +341,7 @@ class ChatViewModelTests: XCTestCase { calls.append(.open($0)) } let viewModel: ChatViewModel = .mock(interactor: .mock(), environment: viewModelEnv) - + let mockUrl = try XCTUnwrap(URL(string: "mock://mock")) viewModel.linkTapped(mockUrl) @@ -403,7 +404,7 @@ class ChatViewModelTests: XCTestCase { typealias FileUploadListViewModel = SecureConversations.FileUploadListViewModel var fileManager = FoundationBased.FileManager.failing fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] } - fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in } + fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in } var transcriptModelEnv = TranscriptModel.Environment.failing transcriptModelEnv.fileManager = fileManager diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests.swift b/GliaWidgetsTests/Sources/Glia/GliaTests.swift index 5f2020671..519fe5bc3 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests.swift @@ -37,9 +37,10 @@ final class GliaTests: XCTestCase { func test__deprecated_start_passes_all_arguments_to_interactor() throws { var gliaEnv = Glia.Environment.failing - gliaEnv.uiDevice.isProximityMonitoringEnabled = { _ in } - gliaEnv.uiScreen.brightness = { 0 } - gliaEnv.uiApplication.isIdleTimerDisabled = { _ in } + var proximityManagerEnv = ProximityManager.Environment.failing + proximityManagerEnv.uiDevice.isProximityMonitoringEnabled = { _ in } + proximityManagerEnv.uiApplication.isIdleTimerDisabled = { _ in } + gliaEnv.proximityManager = .init(environment: proximityManagerEnv) gliaEnv.notificationCenter.removeObserverClosure = { _ in } gliaEnv.notificationCenter.removeObserverWithNameAndObject = { _, _, _ in } gliaEnv.notificationCenter.addObserverClosure = { _, _, _, _ in } @@ -95,15 +96,12 @@ final class GliaTests: XCTestCase { } let sdk = Glia(environment: gliaEnv) - let kind = EngagementKind.audioCall - let queueID = "queueID" - let visitorContext = VisitorContext.mock() try sdk.start( - kind, + .audioCall, configuration: .mock(), - queueID: queueID, - visitorContext: visitorContext, + queueID: "queueID", + visitorContext: .mock(), theme: expectedTheme, features: .all, sceneProvider: expectedSceneProvider @@ -212,6 +210,10 @@ final class GliaTests: XCTestCase { var calls = [Call]() var gliaEnv = Glia.Environment.failing + var proximityManagerEnv = ProximityManager.Environment.failing + proximityManagerEnv.uiDevice.isProximityMonitoringEnabled = { _ in } + proximityManagerEnv.uiApplication.isIdleTimerDisabled = { _ in } + gliaEnv.proximityManager = .init(environment: proximityManagerEnv) gliaEnv.uuid = { .mock } gliaEnv.uiApplication.windows = { [] } gliaEnv.callVisualizerPresenter = .init(presenter: { nil }) @@ -321,8 +323,7 @@ final class GliaTests: XCTestCase { func test_engagementCoordinatorGetsDeallocated() throws { var environment = Glia.Environment.failing - environment.coreSDKConfigurator.configureWithConfiguration = { - _, callback in + environment.coreSDKConfigurator.configureWithConfiguration = { _, callback in callback?() } environment.coreSDKConfigurator.configureWithInteractor = { _ in } diff --git a/GliaWidgetsTests/Sources/ProximityManager/ProximityManager.Failing.swift b/GliaWidgetsTests/Sources/ProximityManager/ProximityManager.Failing.swift new file mode 100644 index 000000000..0c2c05751 --- /dev/null +++ b/GliaWidgetsTests/Sources/ProximityManager/ProximityManager.Failing.swift @@ -0,0 +1,13 @@ +import Foundation +@testable import GliaWidgets + +extension ProximityManager { + static let failing = ProximityManager(environment: .failing) +} + +extension ProximityManager.Environment { + static let failing = Self( + uiApplication: .failing, + uiDevice: .failing + ) +} diff --git a/GliaWidgetsTests/UIKitBased.Failing.swift b/GliaWidgetsTests/UIKitBased.Failing.swift index 6edfaf70b..56c32cd52 100644 --- a/GliaWidgetsTests/UIKitBased.Failing.swift +++ b/GliaWidgetsTests/UIKitBased.Failing.swift @@ -38,14 +38,6 @@ extension UIKitBased.UIApplication { extension UIKitBased.UIScreen { static let failing = Self( - brightness: { - fail("\(Self.self).brightness") - return 0.0 - }, - setBrightness: { _ in - fail("\(Self.self).setBrightness") - return - }, bounds: { fail("\(Self.self).bounds") return CGRect() diff --git a/GliaWidgetsTests/ViewModel/Chat/ChatViewModel.Environment.Failing.swift b/GliaWidgetsTests/ViewModel/Chat/ChatViewModel.Environment.Failing.swift index eb6756ae5..a31eae67b 100644 --- a/GliaWidgetsTests/ViewModel/Chat/ChatViewModel.Environment.Failing.swift +++ b/GliaWidgetsTests/ViewModel/Chat/ChatViewModel.Environment.Failing.swift @@ -53,7 +53,8 @@ extension ChatViewModel.Environment { createSendMessagePayload: { _, _ in fail("\(Self.self).createSendMessagePayload") return .mock() - } + }, + proximityManager: .failing ) } } diff --git a/SnapshotTests/VideoCallViewControllerDynamicTypeFontTests.swift b/SnapshotTests/VideoCallViewControllerDynamicTypeFontTests.swift index 2e9f44ecc..2285e67f0 100644 --- a/SnapshotTests/VideoCallViewControllerDynamicTypeFontTests.swift +++ b/SnapshotTests/VideoCallViewControllerDynamicTypeFontTests.swift @@ -46,7 +46,10 @@ final class VideoCallViewControllerDynamicTypeFontTests: SnapshotTestCase { backButton: .init(style: .mock(image: Asset.back.image)) ) ) - let props: CallVisualizer.VideoCallViewController.Props = .init(videoCallViewProps: videoCallViewProps) + let props: CallVisualizer.VideoCallViewController.Props = .init( + videoCallViewProps: videoCallViewProps, + viewDidLoad: .nop + ) let viewController: CallVisualizer.VideoCallViewController = .mock(props: props) viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) diff --git a/SnapshotTests/VideoCallViewControllerLayoutTests.swift b/SnapshotTests/VideoCallViewControllerLayoutTests.swift index 2efe05f40..4db4b140a 100644 --- a/SnapshotTests/VideoCallViewControllerLayoutTests.swift +++ b/SnapshotTests/VideoCallViewControllerLayoutTests.swift @@ -45,7 +45,10 @@ final class VideoCallViewControllerLayoutTests: SnapshotTestCase { backButton: .init(style: .mock(image: Asset.back.image)) ) ) - let props: CallVisualizer.VideoCallViewController.Props = .init(videoCallViewProps: videoCallViewProps) + let props: CallVisualizer.VideoCallViewController.Props = .init( + videoCallViewProps: videoCallViewProps, + viewDidLoad: .nop + ) let viewController: CallVisualizer.VideoCallViewController = .mock(props: props) viewController.assertSnapshot(as: .image, in: .portrait) diff --git a/SnapshotTests/VideoCallViewControllerVoiceOverTests.swift b/SnapshotTests/VideoCallViewControllerVoiceOverTests.swift index df163f8ae..a5932a160 100644 --- a/SnapshotTests/VideoCallViewControllerVoiceOverTests.swift +++ b/SnapshotTests/VideoCallViewControllerVoiceOverTests.swift @@ -46,7 +46,10 @@ final class VideoCallViewControllerVoiceOverTests: SnapshotTestCase { backButton: .init(style: .mock(image: Asset.back.image)) ) ) - let props: CallVisualizer.VideoCallViewController.Props = .init(videoCallViewProps: videoCallViewProps) + let props: CallVisualizer.VideoCallViewController.Props = .init( + videoCallViewProps: videoCallViewProps, + viewDidLoad: .nop + ) let viewController: CallVisualizer.VideoCallViewController = .mock(props: props) viewController.assertSnapshot(as: .accessibilityImage) From f97c95bb0a4a08108a95efe6e1ea84288e19619f Mon Sep 17 00:00:00 2001 From: Yurii Dukhovnyi Date: Wed, 1 Nov 2023 15:35:26 +0200 Subject: [PATCH 08/12] Remove AppIcon from GliaWidgets SDK AppIcon in SDK assets may lead to replacing integrator application icon with Glia logo that is not acceptable. --- .../AppIcon.appiconset/512x512_light.png | Bin 38290 -> 0 bytes .../AppIcon.appiconset/Contents.json | 14 -------------- 2 files changed, 14 deletions(-) delete mode 100644 GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/512x512_light.png delete mode 100644 GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/512x512_light.png b/GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/512x512_light.png deleted file mode 100644 index 691326203a6ce5ec1a5589923ca2f3af26826acb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38290 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

%4$^ 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 l1oQtbpZ;IdL~>eUVDWH@*M> diff --git a/GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index f281da174..000000000 --- a/GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "images" : [ - { - "filename" : "512x512_light.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} From 1afc597b9ed301cd7fed999db3290cdef945c0ec Mon Sep 17 00:00:00 2001 From: BitriseBot Date: Thu, 2 Nov 2023 11:07:09 +0000 Subject: [PATCH 09/12] Update dependencies declared in `Podfile` and `Package.swift` --- GliaWidgets.podspec | 2 +- GliaWidgetsTests/Sources/InteractorTests.swift | 2 +- Package.swift | 4 ++-- Podfile.lock | 8 ++++---- templates/GliaWidgets.podspec | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/GliaWidgets.podspec b/GliaWidgets.podspec index c058fdd09..433b49008 100644 --- a/GliaWidgets.podspec +++ b/GliaWidgets.podspec @@ -19,5 +19,5 @@ Pod::Spec.new do |s| } s.exclude_files = ['GliaWidgets/Window/**'] - s.dependency 'GliaCoreSDK', '1.1.5' + s.dependency 'GliaCoreSDK', '1.1.6' end diff --git a/GliaWidgetsTests/Sources/InteractorTests.swift b/GliaWidgetsTests/Sources/InteractorTests.swift index 515a1cfc7..78f3d0510 100644 --- a/GliaWidgetsTests/Sources/InteractorTests.swift +++ b/GliaWidgetsTests/Sources/InteractorTests.swift @@ -365,7 +365,7 @@ class InteractorTests: XCTestCase { } } - interactor.end(with: .natural) + interactor.end(with: .operatorHungUp) XCTAssertEqual(callbacks, [.ended]) } diff --git a/Package.swift b/Package.swift index eef33d9d9..b6f5635df 100644 --- a/Package.swift +++ b/Package.swift @@ -31,8 +31,8 @@ let package = Package( ), .binaryTarget( name: "GliaCoreSDK", - url: "https://github.com/salemove/ios-bundle/releases/download/1.1.5/GliaCoreSDK.xcframework.zip", - checksum: "05ea83d9bdddb122962a04662cf6a79033b84d8910f46a6aab9aae5a5d4be58b" + url: "https://github.com/salemove/ios-bundle/releases/download/1.1.6/GliaCoreSDK.xcframework.zip", + checksum: "003d21c155d811ad77b49bf1f30bd6543f2803e5434d148b552c667648a099ad" ), .target( name: "GliaWidgets", diff --git a/Podfile.lock b/Podfile.lock index cc7acf675..c4fad2880 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -7,12 +7,12 @@ PODS: - AccessibilitySnapshot/Core - SnapshotTesting (~> 1.0) - GliaCoreDependency (1.2) - - GliaCoreSDK (1.1.5): + - GliaCoreSDK (1.1.6): - GliaCoreDependency (= 1.2) - TwilioVoice (= 6.3.1) - WebRTC-lib (= 96.0.0) - SnapshotTesting (1.9.0) - - SwiftLint (0.52.0) + - SwiftLint (0.53.0) - TwilioVoice (6.3.1) - WebRTC-lib (96.0.0) @@ -34,9 +34,9 @@ SPEC REPOS: SPEC CHECKSUMS: AccessibilitySnapshot: a91e4a69f870188b51f43863d9fc7269d07cdd93 GliaCoreDependency: 87b3897f0d85321ecf77f1faa829211ad527e54d - GliaCoreSDK: 1cba2761bf4b0205479c7f5cafcc8893444f6c70 + GliaCoreSDK: 62799fd130cba8fca9008363cf3dba73a605c13e SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b - SwiftLint: 13280e21cdda6786ad908dc6e416afe5acd1fcb7 + SwiftLint: 5ce4d6a8ff83f1b5fd5ad5dbf30965d35af65e44 TwilioVoice: 098a959181d4607921f5822d3c9f13043ea4075b WebRTC-lib: 508fe02efa0c1a3a8867082a77d24c9be5d29aeb diff --git a/templates/GliaWidgets.podspec b/templates/GliaWidgets.podspec index 0e9c8fe5d..8bb34ef3a 100644 --- a/templates/GliaWidgets.podspec +++ b/templates/GliaWidgets.podspec @@ -19,5 +19,5 @@ Pod::Spec.new do |s| } s.exclude_files = ['GliaWidgets/Window/**'] - s.dependency 'GliaCoreSDK', '1.1.5' + s.dependency 'GliaCoreSDK', '1.1.6' end From ef25c6bfd8977c31af24bc017af8d1ec9489de1d Mon Sep 17 00:00:00 2001 From: Egor Egorov Date: Thu, 12 Oct 2023 16:28:25 +0300 Subject: [PATCH 10/12] Fix ending CV engagement Previously if WidgetsSDK is configured one time and then visitor has regular engagement, then CV engagement with screen sharing, if the operator ends CV engagement, bubble and screen sharing state is still displayed. It happened because EngagementViewModel called interator.endSession which set `isEngagementEndedByVisitor` to `true` even in case if engagement is ended by operator. Since CV engagement is started without additional `configure` call, interactor keeps `isEngagementEndedByVisitor` as `true`, which was broking CV flow. This commit fixes it. MOB-2731 --- GliaWidgets.xcodeproj/project.pbxproj | 4 +++ GliaWidgets/Public/Glia/Glia.swift | 2 +- .../Sources/Interactor/Interactor.swift | 17 +++++++---- .../ChatViewModel/Mocks/Survey.Mock.swift | 24 +++++++++++++++ GliaWidgetsTests/Sources/Glia/GliaTests.swift | 30 +++++++++++++++++++ .../Sources/InteractorTests.swift | 30 +++++++++++++++++++ 6 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 GliaWidgetsTests/Sources/ChatViewModel/Mocks/Survey.Mock.swift diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index 067669301..18d20f15e 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -351,6 +351,7 @@ 845E2F9D283FCB1400C04D56 /* Theme.Survey.Checkbox.Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845E2F9C283FCB1400C04D56 /* Theme.Survey.Checkbox.Accessibility.swift */; }; 84602A742AE94DE50031E606 /* ProximityManager.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84602A732AE94DE50031E606 /* ProximityManager.Mock.swift */; }; 84602A772AEA5BEA0031E606 /* ProximityManager.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84602A762AEA5BEA0031E606 /* ProximityManager.Failing.swift */; }; + 84602A792AEAB7CA0031E606 /* Survey.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84602A782AEAB7CA0031E606 /* Survey.Mock.swift */; }; 8464297A2A44937600943BD6 /* AlertViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846429792A44937600943BD6 /* AlertViewControllerTests.swift */; }; 8464297D2A459E7D00943BD6 /* UIViewController+Replaceble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8464297C2A459E7D00943BD6 /* UIViewController+Replaceble.swift */; }; 846429802A45A1F200943BD6 /* OfferPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8464297F2A45A1F200943BD6 /* OfferPresenter.swift */; }; @@ -1093,6 +1094,7 @@ 845E2F9C283FCB1400C04D56 /* Theme.Survey.Checkbox.Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.Survey.Checkbox.Accessibility.swift; sourceTree = ""; }; 84602A732AE94DE50031E606 /* ProximityManager.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityManager.Mock.swift; sourceTree = ""; }; 84602A762AEA5BEA0031E606 /* ProximityManager.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityManager.Failing.swift; sourceTree = ""; }; + 84602A782AEAB7CA0031E606 /* Survey.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Survey.Mock.swift; sourceTree = ""; }; 846429792A44937600943BD6 /* AlertViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewControllerTests.swift; sourceTree = ""; }; 8464297C2A459E7D00943BD6 /* UIViewController+Replaceble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Replaceble.swift"; sourceTree = ""; }; 8464297F2A45A1F200943BD6 /* OfferPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfferPresenter.swift; sourceTree = ""; }; @@ -3180,6 +3182,7 @@ isa = PBXGroup; children = ( 84681A972A61853300DD7406 /* GvaOption.Mock.swift */, + 84602A782AEAB7CA0031E606 /* Survey.Mock.swift */, ); path = Mocks; sourceTree = ""; @@ -4940,6 +4943,7 @@ 846429832A45DA7500943BD6 /* AlertViewController+Mock.swift in Sources */, 846A5C4529F6BEFA0049B29F /* GliaTests+StartEngagement.swift in Sources */, 7512A57A27BF9FCD00319DF1 /* ChatViewModelTests.swift in Sources */, + 84602A792AEAB7CA0031E606 /* Survey.Mock.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index b6249ada2..31ce3b2f6 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -338,7 +338,7 @@ extension Glia { case let .videoStreamAdded(stream): self?.callVisualizer.addVideoStream(stream: stream) case let .stateChanged(state): - if state == .ended(.byOperator) { + if case .ended = state { self?.callVisualizer.endSession() self?.onEvent?(.ended) } else if case .engaged = state { diff --git a/GliaWidgets/Sources/Interactor/Interactor.swift b/GliaWidgets/Sources/Interactor/Interactor.swift index 7873b4d39..207d864a0 100644 --- a/GliaWidgets/Sources/Interactor/Interactor.swift +++ b/GliaWidgets/Sources/Interactor/Interactor.swift @@ -59,7 +59,6 @@ class Interactor { var currentEngagement: CoreSdkClient.Engagement? private var observers = [() -> (AnyObject?, EventHandler)]() - private var isEngagementEndedByVisitor = false var state: InteractorState = .none { didSet { @@ -159,8 +158,6 @@ extension Interactor { success: @escaping () -> Void, failure: @escaping (CoreSdkClient.SalemoveError) -> Void ) { - isEngagementEndedByVisitor = true - switch state { case .none: success() @@ -203,10 +200,11 @@ extension Interactor { success: @escaping () -> Void, failure: @escaping (CoreSdkClient.SalemoveError) -> Void ) { - environment.coreSdk.endEngagement { _, error in + environment.coreSdk.endEngagement { [weak self] _, error in if let error = error { failure(error) } else { + self?.state = .ended(.byVisitor) success() } } @@ -335,7 +333,16 @@ extension Interactor: CoreSdkClient.Interactable { func end(with reason: CoreSdkClient.EngagementEndingReason) { currentEngagement = environment.coreSdk.getCurrentEngagement() - state = isEngagementEndedByVisitor == true ? .ended(.byVisitor) : .ended(.byOperator) + switch reason { + case .visitorHungUp: + state = .ended(.byVisitor) + case .operatorHungUp: + state = .ended(.byOperator) + case .error: + state = .ended(.byError) + @unknown default: + state = .ended(.byError) + } } func fail(error: CoreSdkClient.SalemoveError) { diff --git a/GliaWidgetsTests/Sources/ChatViewModel/Mocks/Survey.Mock.swift b/GliaWidgetsTests/Sources/ChatViewModel/Mocks/Survey.Mock.swift new file mode 100644 index 000000000..2c73f171c --- /dev/null +++ b/GliaWidgetsTests/Sources/ChatViewModel/Mocks/Survey.Mock.swift @@ -0,0 +1,24 @@ +import Foundation +@testable import GliaWidgets + +extension CoreSdkClient.Survey { + static func mock( + id: String = "mock", + description: String = "mock", + name: String = "mock", + title: String = "mock", + type: String = "visitor", + siteId: String = "mock" + ) throws -> Self { + let data = try JSONSerialization.data(withJSONObject: [ + "id": id, + "description": description, + "name": name, + "title": title, + "type": type, + "siteId": siteId, + "questions": [] + ]) + return try JSONDecoder().decode(CoreSdkClient.Survey.self, from: data) + } +} diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests.swift b/GliaWidgetsTests/Sources/Glia/GliaTests.swift index 519fe5bc3..e4e3e83ef 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests.swift @@ -353,4 +353,34 @@ final class GliaTests: XCTestCase { XCTAssertNil(sdk.rootCoordinator) XCTAssertNil(rootCoordinator) } + + func test_screenSharingIsStoppedWhenCallVisualizerEngagementIsEnded() throws { + enum Call { case ended } + var calls: [Call] = [] + var gliaEnv = Glia.Environment.failing + let screenShareHandler: ScreenShareHandler = .mock + screenShareHandler.status().value = .started + gliaEnv.screenShareHandler = screenShareHandler + gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, callback in + callback?() + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } + let sdk = Glia(environment: gliaEnv) + try sdk.configure(with: .mock()) {} + sdk.callVisualizer.delegate?(.visitorCodeIsRequested) + sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } + sdk.onEvent = { event in + switch event { + case .ended: + calls.append(.ended) + default: + XCTFail("There is should be no another event") + } + } + sdk.interactor?.state = .ended(.byVisitor) + + XCTAssertEqual(screenShareHandler.status().value, .stopped) + XCTAssertEqual(calls, [.ended]) + } } diff --git a/GliaWidgetsTests/Sources/InteractorTests.swift b/GliaWidgetsTests/Sources/InteractorTests.swift index 78f3d0510..c91c880e2 100644 --- a/GliaWidgetsTests/Sources/InteractorTests.swift +++ b/GliaWidgetsTests/Sources/InteractorTests.swift @@ -447,4 +447,34 @@ class InteractorTests: XCTestCase { XCTAssertEqual(callbacks, [.mediaUpgradeOffered]) } + + func test_endEngagementSetsStateToEndedByVisitor() { + var interactorEnv = Interactor.Environment.failing + interactorEnv.coreSdk.endEngagement = { completion in completion(true, nil) } + let interactor = Interactor.mock(environment: interactorEnv) + interactor.state = .engaged(.mock()) + + interactor.endSession(success: {}, failure: { _ in }) + + XCTAssertEqual(interactor.state, .ended(.byVisitor)) + } + + func test_endWithReasonSetsProperState() { + let interactor = Interactor.failing + interactor.state = .engaged(.mock()) + typealias Item = (reason: CoreSdkClient.EngagementEndingReason, state: InteractorState) + + let items: [Item] = [ + (.visitorHungUp, .ended(.byVisitor)), + (.operatorHungUp, .ended(.byOperator)), + (.error, .ended(.byError)) + ] + + let test: (Item) -> Void = { item in + interactor.end(with: item.reason) + XCTAssertEqual(interactor.state, item.state) + } + + items.forEach(test) + } } From 8032a6ad5af3aeb784f31cf21692fa01cc1c14af Mon Sep 17 00:00:00 2001 From: Egor Egorov Date: Wed, 25 Oct 2023 14:57:40 +0300 Subject: [PATCH 11/12] Introduce new `configure` interface with non-optional `completion` Commit also includes several improvements: - filtering out empty queueIds; - depracation of old `configure` method; - replace checking interactor existance with configuration one to ensure SDK is configured; - improve error handling in TestingApp MOB-2784 --- .../Public/Glia/Glia+StartEngagement.swift | 9 +- GliaWidgets/Public/Glia/Glia.Deprecated.swift | 23 ++++- .../Glia/Glia.RemoteConfiguration.swift | 2 +- GliaWidgets/Public/Glia/Glia.swift | 47 ++++++---- .../CoreSDKClient.Interface.swift | 3 +- .../CoreSDKConfigurator.Interface.swift | 6 +- .../Call/CallViewController.Mock.swift | 2 +- .../Chat/ChatViewController.Mock.swift | 4 +- .../ChatViewModel/ChatViewModelTests.swift | 6 +- .../Glia/GliaTests+StartEngagement.swift | 67 ++++++++----- GliaWidgetsTests/Sources/Glia/GliaTests.swift | 52 +++++++---- .../Sources/InteractorTests.swift | 8 +- .../ViewController/ViewController.swift | 93 ++++++++++++------- 13 files changed, 208 insertions(+), 114 deletions(-) diff --git a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift index 616d4581c..ead149757 100644 --- a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift +++ b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift @@ -18,7 +18,7 @@ extension Glia { /// - `GliaError.engagementExists /// - `GliaError.sdkIsNotConfigured` /// - /// - Important: Note, that `configure(with:queueID:visitorContext:)` must be called initially prior to this method, + /// - Important: Note, that `configure(with:uiConfig:assetsBuilder:completion:)` must be called initially prior to this method, /// because `GliaError.sdkIsNotConfigured` will occur otherwise. /// public func startEngagement( @@ -28,7 +28,10 @@ extension Glia { features: Features = .all, sceneProvider: SceneProvider? = nil ) throws { - guard !queueIds.isEmpty else { throw GliaError.startingEngagementWithNoQueueIdsIsNotAllowed } + let trimmedQueueIds = queueIds + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + guard !trimmedQueueIds.isEmpty else { throw GliaError.startingEngagementWithNoQueueIdsIsNotAllowed } guard engagement == .none else { throw GliaError.engagementExists } guard let configuration = self.configuration else { throw GliaError.sdkIsNotConfigured } if let engagement = environment.coreSdk.getCurrentEngagement(), @@ -39,7 +42,7 @@ extension Glia { // Creates interactor instance let createdInteractor = setupInteractor( configuration: configuration, - queueIds: queueIds + queueIds: trimmedQueueIds ) theme.chat.connect.queue.firstText = companyName( diff --git a/GliaWidgets/Public/Glia/Glia.Deprecated.swift b/GliaWidgets/Public/Glia/Glia.Deprecated.swift index 5675af6f5..dda59f821 100644 --- a/GliaWidgets/Public/Glia/Glia.Deprecated.swift +++ b/GliaWidgets/Public/Glia/Glia.Deprecated.swift @@ -2,7 +2,7 @@ import GliaCoreSDK extension Glia { /// Deprecated. - @available(*, unavailable, message: "Use configure(with:queueId:uiConfig:assetsBuilder:completion:) instead.") + @available(*, unavailable, message: "Use configure(with:uiConfig:assetsBuilder:completion:) instead.") public func configure( with configuration: Configuration, queueId: String, @@ -184,6 +184,27 @@ extension Glia { sceneProvider: sceneProvider ) } + + /// Deprecated, use the `configure` method that provides a `Result` in its completion instead. + @available(*, deprecated, message: "Use the `configure` method that provides a `Result` in its completion instead.") + public func configure( + with configuration: Configuration, + uiConfig: RemoteConfiguration? = nil, + assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard, + completion: (() -> Void)? = nil + ) throws { + try configure( + with: configuration, + uiConfig: uiConfig, + assetsBuilder: assetsBuilder + ) { result in + defer { + completion?() + } + guard case let .failure(error) = result else { return } + debugPrint("💥 Core SDK configuration is not valid. Unexpected error='\(error)'.") + } + } } extension Glia.Authentication { diff --git a/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift b/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift index 9341f171c..d4d42fb32 100644 --- a/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift +++ b/GliaWidgets/Public/Glia/Glia.RemoteConfiguration.swift @@ -18,7 +18,7 @@ extension Glia { /// - `GliaError.engagementExists /// - `GliaError.sdkIsNotConfigured` /// - /// - Important: Note, that `configure(with:queueID:visitorContext:)` must be called initially prior to this method, + /// - Important: Note, that `configure(with:uiConfig:assetsBuilder:completion:)` must be called initially prior to this method, /// because `GliaError.sdkIsNotConfigured` will occur otherwise. /// public func startEngagementWithConfig( diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index 31ce3b2f6..f0ec3cbe8 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -104,23 +104,25 @@ public class Glia { /// Setup SDK using specific engagement configuration without starting the engagement. /// - Parameters: /// - configuration: Engagement configuration. - /// - visitorContext: Visitor context. /// - uiConfig: Remote UI configuration. /// - assetsBuilder: Provides assets for remote configuration. - /// - completion: Optional completion handler that will be fired once configuration is complete. - /// Passing `nil` will defer configuration. Passing closure will start configuration immediately. + /// - completion: Completion handler that will be fired once configuration is complete. public func configure( with configuration: Configuration, uiConfig: RemoteConfiguration? = nil, assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard, - completion: (() -> Void)? = nil + completion: @escaping (Result) -> Void ) throws { guard environment.coreSdk.getCurrentEngagement() == nil else { throw GliaError.configuringDuringEngagementIsNotAllowed } self.uiConfig = uiConfig self.assetsBuilder = assetsBuilder - self.configuration = configuration + // `configuration` should be erased to avoid cases when integrators + // call `configure` and `startEngagement` asynchronously, and + // second-time configuration has not been complete, but `startEngagement` + // is fired and SDK has previous `configuration`. + self.configuration = nil self.callVisualizer.delegate = { action in switch action { @@ -129,10 +131,15 @@ public class Glia { } } - // TODO: - Non-optional completion will be added in MOB-2784 - do { - try environment.coreSDKConfigurator.configureWithConfiguration(configuration) { [weak self] in - guard let self else { return } + try environment.coreSDKConfigurator.configureWithConfiguration(configuration) { [weak self] result in + guard let self else { return } + switch result { + case .success: + // Storing `configuration` needs to be done once configuring SDK is complete + // Otherwise integrator can call `configure` and `startEngagement` + // asynchronously, without waiting configuration completion. + self.configuration = configuration + let getRemoteString = self.environment.coreSdk.localeProvider.getRemoteString self.stringProviding = .init(getRemoteString: getRemoteString) @@ -141,11 +148,11 @@ public class Glia { self.setupInteractor(configuration: configuration) } - completion?() + completion(.success(())) + case let .failure(error): + debugPrint("💥 Core SDK configuration is not valid. Unexpected error='\(error)'.") + completion(.failure(error)) } - } catch { - self.configuration = nil - debugPrint("💥 Core SDK configuration is not valid. Unexpected error='\(error)'.") } } @@ -208,11 +215,11 @@ public class Glia { /// - `GliaCoreSDK.ConfigurationError.invalidEnvironment` /// - `GliaError.sdkIsNotConfigured` /// - /// - Important: Note, that in case of engagement has not been started yet, `configure(with:queueID:visitorContext:)` must be called initially prior to this method, + /// - Important: Note, that in case of engagement has not been started yet, `configure(with:uiConfig:assetsBuilder:completion:)` must be called initially prior to this method, /// because `GliaError.sdkIsNotConfigured` will occur otherwise. /// public func fetchVisitorInfo(completion: @escaping (Result) -> Void) { - guard interactor != nil else { + guard configuration != nil else { completion(.failure(GliaError.sdkIsNotConfigured)) return } @@ -242,14 +249,14 @@ public class Glia { /// - `GliaCoreSDK.ConfigurationError.invalidEnvironment` /// - `GliaError.sdkIsNotConfigured` /// - /// - Important: Note, that in case of engagement has not been started yet, `configure(with:queueID:visitorContext:)` must be called initially prior to this method, + /// - Important: Note, that in case of engagement has not been started yet, `configure(with:uiConfig:assetsBuilder:completion:)` must be called initially prior to this method, /// because `GliaError.sdkIsNotConfigured` will occur otherwise. /// public func updateVisitorInfo( _ info: VisitorInfoUpdate, completion: @escaping (Result) -> Void ) { - guard interactor != nil else { + guard configuration != nil else { completion(.failure(GliaError.sdkIsNotConfigured)) return } @@ -263,7 +270,7 @@ public class Glia { rootCoordinator = nil } - guard interactor != nil else { + guard configuration != nil else { completion(.failure(GliaError.sdkIsNotConfigured)) return } @@ -278,10 +285,10 @@ public class Glia { /// It is also possible to monitor Queues changes with [subscribeForUpdates](x-source-tag://subscribeForUpdates) method. /// If the request is unsuccessful for any reason then the completion will have an Error. /// - Parameters: - /// - completion: A callback that will return the Result struct with `Queue` list or `GliaCoreError` + /// - completion: A callback that will return the Result struct with `Queue` list or `GliaCoreError`. /// public func listQueues(_ completion: @escaping (Result<[Queue], Error>) -> Void) { - guard interactor != nil else { + guard configuration != nil else { completion(.failure(GliaError.sdkIsNotConfigured)) return } diff --git a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift index cb14acb1c..d734c0285 100644 --- a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift +++ b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift @@ -19,7 +19,7 @@ struct CoreSdkClient { typealias ConfigureWithConfiguration = ( _ sdkConfiguration: Self.Salemove.Configuration, - _ completion: (() -> Void)? + _ completion: @escaping Self.ConfigureCompletion ) -> Void var configureWithConfiguration: ConfigureWithConfiguration @@ -249,4 +249,5 @@ extension CoreSdkClient { typealias Cancellable = GliaCore.Cancellable typealias ReactiveSwift = GliaCoreDependency.ReactiveSwift typealias SendMessagePayload = GliaCoreSDK.SendMessagePayload + typealias ConfigureCompletion = GliaCoreSDK.GliaCore.ConfigureCompletion } diff --git a/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift index 80f5fed8d..3fa83ef96 100644 --- a/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift +++ b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift @@ -2,7 +2,7 @@ import Foundation struct CoreSDKConfigurator { var configureWithInteractor: CoreSdkClient.ConfigureWithInteractor - var configureWithConfiguration: (Configuration, (() -> Void)?) throws -> Void + var configureWithConfiguration: (Configuration, @escaping (Result) -> Void) throws -> Void } extension CoreSDKConfigurator { @@ -16,9 +16,7 @@ extension CoreSDKConfigurator { authorizingMethod: configuration.authorizationMethod.coreAuthorizationMethod, pushNotifications: configuration.pushNotifications.coreSdk ) - coreSdk.configureWithConfiguration(sdkConfiguration) { - completion?() - } + coreSdk.configureWithConfiguration(sdkConfiguration, completion) } ) } diff --git a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift index 3a0983729..c4f6cdf3f 100644 --- a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift @@ -86,7 +86,7 @@ extension CallViewController { let queueId = UUID.mock.uuidString var interactorEnv = Interactor.Environment.mock interactorEnv.coreSdk.configureWithConfiguration = { _, callback in - callback?() + callback(.success(())) } let interactor = Interactor.mock( queueId: queueId, diff --git a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift index a4fb31855..d6ef76044 100644 --- a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift @@ -199,7 +199,7 @@ extension ChatViewController { chatViewModelEnv.fetchChatHistory = { $0(.success([])) } var interEnv = Interactor.Environment.mock interEnv.coreSdk.configureWithConfiguration = { _, callback in - callback?() + callback(.success(())) } let interactor = Interactor.mock(environment: interEnv) let generateUUID = UUID.incrementing @@ -540,7 +540,7 @@ extension ChatViewController { chatViewModelEnv.fetchChatHistory = { $0(.success(messages)) } var interEnv = Interactor.Environment.mock interEnv.coreSdk.configureWithConfiguration = { _, callback in - callback?() + callback(.success(())) } let interactor = Interactor.mock(environment: interEnv) let chatViewModel = ChatViewModel.mock(interactor: interactor, environment: chatViewModelEnv) diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift index fd7479211..6b289fa2a 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift @@ -367,7 +367,7 @@ class ChatViewModelTests: XCTestCase { // we use the mocked one, which doesn't execute the completion, // `sendMessageWithAttachment` will not be executed. environment.coreSdk.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } let viewModel: ChatViewModel = .mock( @@ -595,7 +595,7 @@ class ChatViewModelTests: XCTestCase { let interactor = Interactor.failing interactor.environment.gcd.mainQueue.asyncIfNeeded = { $0() } interactor.environment.coreSdk.configureWithInteractor = { _ in } - interactor.environment.coreSdk.configureWithConfiguration = { _, callback in callback?() } + interactor.environment.coreSdk.configureWithConfiguration = { _, callback in callback(.success(())) } interactor.environment.coreSdk.sendMessagePreview = { _, _ in } interactor.environment.coreSdk.sendMessageWithMessagePayload = { _, completion in completion(.success(.mock(id: expectedMessageId))) @@ -645,7 +645,7 @@ class ChatViewModelTests: XCTestCase { let interactor = Interactor.failing interactor.environment.gcd.mainQueue.asyncIfNeeded = { $0() } interactor.environment.coreSdk.configureWithInteractor = { _ in } - interactor.environment.coreSdk.configureWithConfiguration = { _, callback in callback?() } + interactor.environment.coreSdk.configureWithConfiguration = { _, callback in callback(.success(())) } interactor.environment.coreSdk.sendMessagePreview = { _, _ in } interactor.environment.coreSdk.sendMessageWithMessagePayload = { [weak viewModel] _, completion in // Deliver message via socket before REST API response. diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift index cd49506ad..babd2907d 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift @@ -11,7 +11,7 @@ extension GliaTests { func testStartEngagementThrowsErrorWhenEngagementAlreadyExists() throws { var sdkEnv = Glia.Environment.failing sdkEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } let sdk = Glia(environment: sdkEnv) sdk.rootCoordinator = .mock(engagementKind: .chat, screenShareHandler: .mock) @@ -30,9 +30,9 @@ extension GliaTests { func testStartEngagementThrowsErrorDuringActiveCallVisualizerEngagement() throws { let sdk = Glia(environment: .failing) sdk.environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } - try sdk.configure(with: .mock()) + try sdk.configure(with: .mock()) {} sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } XCTAssertThrowsError( @@ -67,7 +67,7 @@ extension GliaTests { } environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in calls.append(.configureWithConfiguration) - completion?() + completion(.success(())) } environment.coreSdk.localeProvider.getRemoteString = { _ in nil } environment.createRootCoordinator = { _, _, _, _, _, _, _ in @@ -99,7 +99,7 @@ extension GliaTests { ) } environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.localeProvider.getRemoteString = { _ in nil } @@ -110,8 +110,13 @@ extension GliaTests { theme.call.connect.queue.firstText = "Glia 1" theme.chat.connect.queue.firstText = "Glia 2" - try sdk.configure(with: .mock()) - try sdk.startEngagement(engagementKind: .chat, in: ["queueId"], theme: theme) + try sdk.configure(with: .mock()) { + do { + try sdk.startEngagement(engagementKind: .chat, in: ["queueId"], theme: theme) + } catch { + XCTFail("startEngagement unexpectedly failed with error \(error), but should succeed instead.") + } + } let configuredSdkTheme = resultingViewFactory?.theme XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Glia 1") @@ -138,7 +143,7 @@ extension GliaTests { environment.coreSdk.localeProvider.getRemoteString = { _ in "Glia" } environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.getCurrentEngagement = { nil } @@ -150,8 +155,13 @@ extension GliaTests { theme.call.connect.queue.firstText = "Glia 1" theme.chat.connect.queue.firstText = "Glia 2" - try sdk.configure(with: .mock()) { } - try sdk.startEngagement(engagementKind: .chat, in: ["queueId"], theme: theme) + try sdk.configure(with: .mock()) { + do { + try sdk.startEngagement(engagementKind: .chat, in: ["queueId"], theme: theme) + } catch { + XCTFail("startEngagement unexpectedly failed with error \(error), but should succeed instead.") + } + } let configuredSdkTheme = resultingViewFactory?.theme XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Glia") @@ -176,15 +186,20 @@ extension GliaTests { ) } environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.localeProvider.getRemoteString = { _ in nil } let sdk = Glia(environment: environment) - try sdk.configure(with: .mock(companyName: "Glia")) - try sdk.startEngagement(engagementKind: .chat, in: ["queueId"]) + try sdk.configure(with: .mock(companyName: "Glia")) { + do { + try sdk.startEngagement(engagementKind: .chat, in: ["queueId"]) + } catch { + XCTFail("startEngagement unexpectedly failed with error \(error), but should succeed instead.") + } + } let configuredSdkTheme = resultingViewFactory?.theme XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Glia") @@ -209,15 +224,20 @@ extension GliaTests { ) } environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.localeProvider.getRemoteString = { _ in nil } let sdk = Glia(environment: environment) - try sdk.configure(with: .mock()) - try sdk.startEngagement(engagementKind: .chat, in: ["queueId"]) + try sdk.configure(with: .mock()) { + do { + try sdk.startEngagement(engagementKind: .chat, in: ["queueId"]) + } catch { + XCTFail("startEngagement unexpectedly failed with error \(error), but should succeed instead.") + } + } let configuredSdkTheme = resultingViewFactory?.theme XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Company Name") @@ -244,7 +264,7 @@ extension GliaTests { environment.coreSdk.localeProvider.getRemoteString = { _ in "" } environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.getCurrentEngagement = { nil } @@ -255,8 +275,13 @@ extension GliaTests { theme.call.connect.queue.firstText = "Glia 1" theme.chat.connect.queue.firstText = "Glia 2" - try sdk.configure(with: .mock()) - try sdk.startEngagement(engagementKind: .chat, in: ["queueId"], theme: theme) + try sdk.configure(with: .mock()) { + do { + try sdk.startEngagement(engagementKind: .chat, in: ["queueId"], theme: theme) + } catch { + XCTFail("startEngagement unexpectedly failed with error \(error), but should succeed instead.") + } + } let configuredSdkTheme = resultingViewFactory?.theme XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Glia 1") @@ -288,14 +313,14 @@ extension GliaTests { Glia.sharedInstance.stringProviding = StringProviding( getRemoteString: environment.coreSdk.localeProvider.getRemoteString ) - completion?() + completion(.success(())) } environment.coreSdk.getCurrentEngagement = { nil } let sdk = Glia(environment: environment) try sdk.configure(with: .mock(), completion: {}) try sdk.startEngagement(engagementKind: .chat, in: ["queueId"]) - + let configuredSdkTheme = resultingViewFactory?.theme let localFallbackCompanyName = "Company Name" XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, localFallbackCompanyName) diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests.swift b/GliaWidgetsTests/Sources/Glia/GliaTests.swift index e4e3e83ef..25b7a9ebe 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests.swift @@ -52,12 +52,11 @@ final class GliaTests: XCTestCase { gliaEnv.createFileUploadListModel = { _ in .mock() } gliaEnv.coreSdk.localeProvider.getRemoteString = { _ in nil } gliaEnv.coreSdk.authentication = { _ in .mock } - gliaEnv.coreSdk.configureWithConfiguration = { _, callback in callback?() } gliaEnv.coreSdk.queueForEngagement = { _, callback in callback(.success(.mock)) } gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } @@ -130,7 +129,7 @@ final class GliaTests: XCTestCase { var gliaEnv = Glia.Environment.failing gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } @@ -138,7 +137,7 @@ final class GliaTests: XCTestCase { sdk.onEvent = { calls.append(.onEvent($0)) } - try sdk.configure(with: .mock()) + try sdk.configure(with: .mock()) {} sdk.callVisualizer.delegate?(.visitorCodeIsRequested) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } @@ -158,7 +157,7 @@ final class GliaTests: XCTestCase { gliaEnv.coreSdk.configureWithConfiguration = { _, _ in } gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } @@ -166,7 +165,7 @@ final class GliaTests: XCTestCase { sdk.onEvent = { calls.append(.onEvent($0)) } - try sdk.configure(with: .mock()) + try sdk.configure(with: .mock()) {} sdk.callVisualizer.delegate?(.visitorCodeIsRequested) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } @@ -185,7 +184,7 @@ final class GliaTests: XCTestCase { gliaEnv.callVisualizerPresenter = .init(presenter: { nil }) gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } @@ -193,7 +192,7 @@ final class GliaTests: XCTestCase { sdk.onEvent = { calls.append(.onEvent($0)) } - try sdk.configure(with: .mock()) + try sdk.configure(with: .mock()) {} sdk.callVisualizer.delegate?(.visitorCodeIsRequested) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } @@ -220,7 +219,7 @@ final class GliaTests: XCTestCase { gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } gliaEnv.notificationCenter.addObserverClosure = { _, _, _, _ in } gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) } gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } @@ -228,7 +227,7 @@ final class GliaTests: XCTestCase { sdk.onEvent = { calls.append(.onEvent($0)) } - try sdk.configure(with: .mock()) + try sdk.configure(with: .mock()) {} sdk.callVisualizer.delegate?(.visitorCodeIsRequested) sdk.environment.coreSdk.getCurrentEngagement = { .mock(source: .callVisualizer) } @@ -243,7 +242,7 @@ final class GliaTests: XCTestCase { environment.coreSdk.getCurrentEngagement = { .mock() } let sdk = Glia(environment: environment) - XCTAssertThrowsError(try sdk.configure(with: .mock(), queueId: "queueId")) { error in + XCTAssertThrowsError(try sdk.configure(with: .mock()) {}) { error in XCTAssertEqual(error as? GliaError, GliaError.configuringDuringEngagementIsNotAllowed) } } @@ -304,27 +303,46 @@ final class GliaTests: XCTestCase { func test_isConfiguredIsTrueWhenConfigurationPerformed() throws { var environment = Glia.Environment.failing environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in - completion?() + completion(.success(())) + } + let sdk = Glia(environment: environment) + try sdk.configure(with: .mock()) {} + XCTAssertTrue(sdk.isConfigured) + } + + func test_isConfiguredIsFalseWhenSecondConfigureCallThrowsError() throws { + var environment = Glia.Environment.failing + var isFirstConfigure = true + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + if isFirstConfigure { + isFirstConfigure = false + completion(.success(())) + } else { + throw CoreSdkClient.GliaCoreError.mock() + } } let sdk = Glia(environment: environment) - try sdk.configure(with: .mock()) + try sdk.configure(with: .mock()) {} XCTAssertTrue(sdk.isConfigured) + + try? sdk.configure(with: .mock()) {} + XCTAssertFalse(sdk.isConfigured) } - func test_isConfiguredIsFalseWhenConfigureWithConfigurationThrowsError() throws { + func test_isConfiguredIsFalseWhenConfigureWithConfigurationThrowsError() { var environment = Glia.Environment.failing environment.coreSDKConfigurator.configureWithConfiguration = { _, _ in throw CoreSdkClient.GliaCoreError.mock() } let sdk = Glia(environment: environment) - try sdk.configure(with: .mock()) + try? sdk.configure(with: .mock()) {} XCTAssertFalse(sdk.isConfigured) } func test_engagementCoordinatorGetsDeallocated() throws { var environment = Glia.Environment.failing environment.coreSDKConfigurator.configureWithConfiguration = { _, callback in - callback?() + callback(.success(())) } environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.localeProvider.getRemoteString = { _ in nil } @@ -363,7 +381,7 @@ final class GliaTests: XCTestCase { gliaEnv.screenShareHandler = screenShareHandler gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, callback in - callback?() + callback(.success(())) } gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } let sdk = Glia(environment: gliaEnv) diff --git a/GliaWidgetsTests/Sources/InteractorTests.swift b/GliaWidgetsTests/Sources/InteractorTests.swift index c91c880e2..44da62bab 100644 --- a/GliaWidgetsTests/Sources/InteractorTests.swift +++ b/GliaWidgetsTests/Sources/InteractorTests.swift @@ -99,7 +99,7 @@ class InteractorTests: XCTestCase { var callbacks: [Callback] = [] var interactorEnv = Interactor.Environment.failing interactorEnv.coreSdk.configureWithInteractor = { _ in } - interactorEnv.coreSdk.configureWithConfiguration = { $1?() } + interactorEnv.coreSdk.configureWithConfiguration = { $1(.success(())) } interactorEnv.coreSdk.queueForEngagement = { _, _ in } interactorEnv.gcd = .mock let interactor = Interactor.mock(environment: interactorEnv) @@ -131,7 +131,7 @@ class InteractorTests: XCTestCase { var callbacks: [Callback] = [] var interactorEnv = Interactor.Environment.failing interactorEnv.coreSdk.configureWithInteractor = { _ in } - interactorEnv.coreSdk.configureWithConfiguration = { $1?() } + interactorEnv.coreSdk.configureWithConfiguration = { $1(.success(())) } interactorEnv.coreSdk.queueForEngagement = { _, completion in completion(.success(.mock)) } @@ -166,7 +166,7 @@ class InteractorTests: XCTestCase { var callbacks: [Callback] = [] var interactorEnv = Interactor.Environment.failing interactorEnv.coreSdk.configureWithInteractor = { _ in } - interactorEnv.coreSdk.configureWithConfiguration = { $1?() } + interactorEnv.coreSdk.configureWithConfiguration = { $1(.success(())) } interactorEnv.coreSdk.queueForEngagement = { _, completion in completion(.failure(.mock())) } @@ -380,7 +380,7 @@ class InteractorTests: XCTestCase { callbacks.append(.sendMessageWithAttachment) } interactorEnv.coreSdk.configureWithInteractor = { _ in } - interactorEnv.coreSdk.configureWithConfiguration = { $1?() } + interactorEnv.coreSdk.configureWithConfiguration = { $1(.success(())) } let interactor = Interactor.mock(environment: interactorEnv) interactor.send(messagePayload: .mock(content: "mock-message")) { result in diff --git a/TestingApp/ViewController/ViewController.swift b/TestingApp/ViewController/ViewController.swift index 2d28283ca..e90b66357 100644 --- a/TestingApp/ViewController/ViewController.swift +++ b/TestingApp/ViewController/ViewController.swift @@ -90,19 +90,22 @@ class ViewController: UIViewController { } private func showErrorAlert(using error: Error) { - guard let gliaError = error as? GliaError else { return } - - switch gliaError { - case GliaError.engagementExists: - alert(message: "Failed to start\nEngagement is ongoing, please use 'Resume' button") - case GliaError.engagementNotExist: - alert(message: "Failed to start\nNo ongoing engagement. Please start a new one with 'Start chat' button") - case GliaError.callVisualizerEngagementExists: - alert(message: "Failed to start\nCall Visualizer engagement is ongoing") - case GliaError.configuringDuringEngagementIsNotAllowed: - alert(message: "The operation couldn't be completed. '\(gliaError)'.") - default: - alert(message: "Failed to start\nCheck Glia parameters in Settings") + if let gliaError = error as? GliaError { + + switch gliaError { + case GliaError.engagementExists: + alert(message: "Failed to start\nEngagement is ongoing, please use 'Resume' button") + case GliaError.engagementNotExist: + alert(message: "Failed to start\nNo ongoing engagement. Please start a new one with 'Start chat' button") + case GliaError.callVisualizerEngagementExists: + alert(message: "Failed to start\nCall Visualizer engagement is ongoing") + case GliaError.configuringDuringEngagementIsNotAllowed: + alert(message: "The operation couldn't be completed. '\(gliaError)'.") + default: + alert(message: "Failed to start\nCheck Glia parameters in Settings") + } + } else { + alert(message: "Failed to execute with error: \(error)\nCheck Glia parameters in Settings") } } @@ -127,15 +130,18 @@ class ViewController: UIViewController { } @IBAction private func endEngagementTapped() { - self.catchingError { - // Since ending of engagement is possible - // only if such engagement exists, we need - // to configure SDK, and only then attempt - // to end engagement. - try Glia.sharedInstance.configure(with: configuration) { + // Since ending of engagement is possible + // only if such engagement exists, we need + // to configure SDK, and only then attempt + // to end engagement. + configureSDK(uiConfig: nil) { [weak self] result in + switch result { + case .success: Glia.sharedInstance.endEngagement { result in print("End engagement operation has been executed. Result='\(result)'.") } + case let .failure(error): + self?.showErrorAlert(using: error) } } } @@ -237,9 +243,15 @@ extension ViewController { do { try Glia.sharedInstance.configure( with: configuration - ) { - completionBlock("SDK has been configured") - completion?(.success(())) + ) { result in + switch result { + case .success: + completionBlock("SDK has been configured") + completion?(.success(())) + case let .failure(error): + completionBlock(error) + completion?(.failure(error)) + } } } catch { completionBlock(error) @@ -288,18 +300,30 @@ extension ViewController { } private func startEngagement(with kind: EngagementKind, config name: String) { - try? Glia.sharedInstance.configure( - with: configuration, - queueId: queueId - ) - guard let config = retrieveRemoteConfiguration(name) else { return } - try? Glia.sharedInstance.startEngagementWithConfig( - engagement: kind, - in: [queueId], - uiConfig: config - ) + let startEngagement = { + self.catchingError { + try Glia.sharedInstance.startEngagementWithConfig( + engagement: kind, + in: [self.queueId], + uiConfig: config + ) + } + } + + if autoConfigureSdkToggle.isOn { + configureSDK(uiConfig: nil) { [weak self] result in + switch result { + case .success: + startEngagement() + case let .failure(error): + self?.showErrorAlert(using: error) + } + } + } else { + startEngagement() + } } private func jsonNames() -> [String] { @@ -379,10 +403,7 @@ extension ViewController { @IBAction private func toggleAuthentication() { catchingError { - try Glia.sharedInstance.configure( - with: configuration, - queueId: queueId - ) { [weak self] in + try Glia.sharedInstance.configure(with: configuration) { [weak self] in guard let self = self else { return } self.catchingError { let authentication = try Self.authentication() From c18710696000843487d629e2a200efa2327becac Mon Sep 17 00:00:00 2001 From: Gerson Noboa Date: Thu, 2 Nov 2023 13:06:03 +0200 Subject: [PATCH 12/12] Add manual locale override capability The configuration accepts a manual locale override, which is `nil` by default. If the Core SDK returns an incorrect manual locale override, then the Widgets SDK also returns an error on configuration to let know the integrator about it. If the integrator provides a wrong site API key, this commit also makes sure to inform the integrator instead of hanging. Also, the settings screen now includes the possibility to add a manual locale override visually. If it's empty, then it sends nil. MOB-2798 --- GliaWidgets/Localization.swift | 304 +++++++++--------- .../Configuration/Configuration+Mock.swift | 6 +- .../Public/Configuration/Configuration.swift | 10 +- GliaWidgets/Public/Glia/Glia.swift | 18 +- GliaWidgets/Public/GliaError.swift | 2 + .../Coordinators/Chat/ChatCoordinator.swift | 1 - .../CoreSDKClient.Interface.swift | 1 + .../CoreSDKConfigurator.Interface.swift | 3 +- .../Extensions/Configuration.Extensions.swift | 22 +- .../Settings/SettingsViewController.swift | 17 +- .../ViewController/ViewController.swift | 38 ++- swiftgen-strings.stencil | 4 +- 12 files changed, 243 insertions(+), 183 deletions(-) diff --git a/GliaWidgets/Localization.swift b/GliaWidgets/Localization.swift index c8f4390c9..c28314b6a 100644 --- a/GliaWidgets/Localization.swift +++ b/GliaWidgets/Localization.swift @@ -13,32 +13,32 @@ internal enum Localization { internal enum Alert { internal enum Action { /// Settings - internal static let settings = Localization.tr("Localizable", "alert.action.settings", fallback: "Settings") + internal static var settings: String { Localization.tr("Localizable", "alert.action.settings", fallback: "Settings") } } internal enum CameraAccess { /// Unable to access camera - internal static let error = Localization.tr("Localizable", "alert.camera_access.error", fallback: "Unable to access camera") + internal static var error: String { Localization.tr("Localizable", "alert.camera_access.error", fallback: "Unable to access camera") } } internal enum MediaSourceAccess { /// Unable to access media source - internal static let error = Localization.tr("Localizable", "alert.media_source_access.error", fallback: "Unable to access media source") + internal static var error: String { Localization.tr("Localizable", "alert.media_source_access.error", fallback: "Unable to access media source") } } internal enum MicrophoneAccess { /// Unable to access microphone - internal static let error = Localization.tr("Localizable", "alert.microphone_access.error", fallback: "Unable to access microphone") + internal static var error: String { Localization.tr("Localizable", "alert.microphone_access.error", fallback: "Unable to access microphone") } } internal enum ScreenSharing { internal enum Start { /// Start Screen Sharing - internal static let header = Localization.tr("Localizable", "alert.screen_sharing.start.header", fallback: "Start Screen Sharing") + internal static var header: String { Localization.tr("Localizable", "alert.screen_sharing.start.header", fallback: "Start Screen Sharing") } /// {operatorName} has asked you to share your screen. - internal static let message = Localization.tr("Localizable", "alert.screen_sharing.start.message", fallback: "{operatorName} has asked you to share your screen.") + internal static var message: String { Localization.tr("Localizable", "alert.screen_sharing.start.message", fallback: "{operatorName} has asked you to share your screen.") } } internal enum Stop { /// Stop Screen Sharing? - internal static let header = Localization.tr("Localizable", "alert.screen_sharing.stop.header", fallback: "Stop Screen Sharing?") + internal static var header: String { Localization.tr("Localizable", "alert.screen_sharing.stop.header", fallback: "Stop Screen Sharing?") } /// Are you sure you want to stop sharing your screen? - internal static let message = Localization.tr("Localizable", "alert.screen_sharing.stop.message", fallback: "Are you sure you want to stop sharing your screen?") + internal static var message: String { Localization.tr("Localizable", "alert.screen_sharing.stop.message", fallback: "Are you sure you want to stop sharing your screen?") } } } } @@ -46,9 +46,9 @@ internal enum Localization { internal enum Bubble { internal enum Accessibility { /// Expands call view. - internal static let hint = Localization.tr("Localizable", "call.bubble.accessibility.hint", fallback: "Expands call view.") + internal static var hint: String { Localization.tr("Localizable", "call.bubble.accessibility.hint", fallback: "Expands call view.") } /// Go back to the engagement. - internal static let label = Localization.tr("Localizable", "call.bubble.accessibility.label", fallback: "Go back to the engagement.") + internal static var label: String { Localization.tr("Localizable", "call.bubble.accessibility.label", fallback: "Go back to the engagement.") } } } internal enum Buttons { @@ -57,13 +57,13 @@ internal enum Localization { internal enum MultipleItems { internal enum Accessibility { /// {badgeValue} unread messages - internal static let label = Localization.tr("Localizable", "call.buttons.chat.badge_value.multiple_items.accessibility.label", fallback: "{badgeValue} unread messages") + internal static var label: String { Localization.tr("Localizable", "call.buttons.chat.badge_value.multiple_items.accessibility.label", fallback: "{badgeValue} unread messages") } } } internal enum SingleItem { internal enum Accessibility { /// {badgeValue} unread message - internal static let label = Localization.tr("Localizable", "call.buttons.chat.badge_value.single_item.accessibility.label", fallback: "{badgeValue} unread message") + internal static var label: String { Localization.tr("Localizable", "call.buttons.chat.badge_value.single_item.accessibility.label", fallback: "{badgeValue} unread message") } } } } @@ -73,20 +73,20 @@ internal enum Localization { internal enum FirstText { internal enum Accessibility { /// Displays operator name. - internal static let hint = Localization.tr("Localizable", "call.connect.first_text.accessibility.hint", fallback: "Displays operator name.") + internal static var hint: String { Localization.tr("Localizable", "call.connect.first_text.accessibility.hint", fallback: "Displays operator name.") } } } internal enum SecondText { internal enum Accessibility { /// Displays call duration. - internal static let hint = Localization.tr("Localizable", "call.connect.second_text.accessibility.hint", fallback: "Displays call duration.") + internal static var hint: String { Localization.tr("Localizable", "call.connect.second_text.accessibility.hint", fallback: "Displays call duration.") } } } } internal enum Duration { internal enum Accessibility { /// Call duration. - internal static let label = Localization.tr("Localizable", "call.duration.accessibility.label", fallback: "Call duration.") + internal static var label: String { Localization.tr("Localizable", "call.duration.accessibility.label", fallback: "Call duration.") } } } internal enum Header { @@ -94,470 +94,470 @@ internal enum Localization { internal enum Button { internal enum Accessibility { /// Minimizes call view. - internal static let hint = Localization.tr("Localizable", "call.header.back.button.accessibility.hint", fallback: "Minimizes call view.") + internal static var hint: String { Localization.tr("Localizable", "call.header.back.button.accessibility.hint", fallback: "Minimizes call view.") } } } } } internal enum Mute { /// Mute - internal static let button = Localization.tr("Localizable", "call.mute.button", fallback: "Mute") + internal static var button: String { Localization.tr("Localizable", "call.mute.button", fallback: "Mute") } } internal enum OnHold { /// You can continue browsing while you are on hold - internal static let bottomText = Localization.tr("Localizable", "call.on_hold.bottom_text", fallback: "You can continue browsing while you are on hold") + internal static var bottomText: String { Localization.tr("Localizable", "call.on_hold.bottom_text", fallback: "You can continue browsing while you are on hold") } /// On Hold - internal static let icon = Localization.tr("Localizable", "call.on_hold.icon", fallback: "On Hold") + internal static var icon: String { Localization.tr("Localizable", "call.on_hold.icon", fallback: "On Hold") } } internal enum OperatorAvatar { internal enum Accessibility { /// Shows operator picture. - internal static let hint = Localization.tr("Localizable", "call.operator_avatar.accessibility.hint", fallback: "Shows operator picture.") + internal static var hint: String { Localization.tr("Localizable", "call.operator_avatar.accessibility.hint", fallback: "Shows operator picture.") } /// Operator Picture - internal static let label = Localization.tr("Localizable", "call.operator_avatar.accessibility.label", fallback: "Operator Picture") + internal static var label: String { Localization.tr("Localizable", "call.operator_avatar.accessibility.label", fallback: "Operator Picture") } } } internal enum OperatorName { internal enum Accessibility { /// Shows operator name. - internal static let hint = Localization.tr("Localizable", "call.operator_name.accessibility.hint", fallback: "Shows operator name.") + internal static var hint: String { Localization.tr("Localizable", "call.operator_name.accessibility.hint", fallback: "Shows operator name.") } } } internal enum OperatorVideo { internal enum Accessibility { /// Operator's Video - internal static let label = Localization.tr("Localizable", "call.operator_video.accessibility.label", fallback: "Operator's Video") + internal static var label: String { Localization.tr("Localizable", "call.operator_video.accessibility.label", fallback: "Operator's Video") } } } internal enum Speaker { /// Speaker - internal static let button = Localization.tr("Localizable", "call.speaker.button", fallback: "Speaker") + internal static var button: String { Localization.tr("Localizable", "call.speaker.button", fallback: "Speaker") } } internal enum Unmute { /// Unmute - internal static let button = Localization.tr("Localizable", "call.unmute.button", fallback: "Unmute") + internal static var button: String { Localization.tr("Localizable", "call.unmute.button", fallback: "Unmute") } } internal enum VisitorVideo { internal enum Accessibility { /// Your Video - internal static let label = Localization.tr("Localizable", "call.visitor_video.accessibility.label", fallback: "Your Video") + internal static var label: String { Localization.tr("Localizable", "call.visitor_video.accessibility.label", fallback: "Your Video") } } } } internal enum CallVisualizer { internal enum ScreenSharing { /// Your Screen is Being Shared - internal static let message = Localization.tr("Localizable", "call_visualizer.screen_sharing.message", fallback: "Your Screen is Being Shared") + internal static var message: String { Localization.tr("Localizable", "call_visualizer.screen_sharing.message", fallback: "Your Screen is Being Shared") } internal enum Header { /// Screen Sharing - internal static let title = Localization.tr("Localizable", "call_visualizer.screen_sharing.header.title", fallback: "Screen Sharing") + internal static var title: String { Localization.tr("Localizable", "call_visualizer.screen_sharing.header.title", fallback: "Screen Sharing") } } } internal enum VisitorCode { /// Your Visitor Code - internal static let title = Localization.tr("Localizable", "call_visualizer.visitor_code.title", fallback: "Your Visitor Code") + internal static var title: String { Localization.tr("Localizable", "call_visualizer.visitor_code.title", fallback: "Your Visitor Code") } internal enum Close { internal enum Accessibility { /// Closes the visitor code - internal static let hint = Localization.tr("Localizable", "call_visualizer.visitor_code.close.accessibility.hint", fallback: "Closes the visitor code") + internal static var hint: String { Localization.tr("Localizable", "call_visualizer.visitor_code.close.accessibility.hint", fallback: "Closes the visitor code") } } } internal enum Refresh { internal enum Accessibility { /// Generates a new visitor code - internal static let hint = Localization.tr("Localizable", "call_visualizer.visitor_code.refresh.accessibility.hint", fallback: "Generates a new visitor code") + internal static var hint: String { Localization.tr("Localizable", "call_visualizer.visitor_code.refresh.accessibility.hint", fallback: "Generates a new visitor code") } /// Refresh Button - internal static let label = Localization.tr("Localizable", "call_visualizer.visitor_code.refresh.accessibility.label", fallback: "Refresh Button") + internal static var label: String { Localization.tr("Localizable", "call_visualizer.visitor_code.refresh.accessibility.label", fallback: "Refresh Button") } } } internal enum Title { internal enum Accessibility { /// Shows the five-digit visitor code. - internal static let hint = Localization.tr("Localizable", "call_visualizer.visitor_code.title.accessibility.hint", fallback: "Shows the five-digit visitor code.") + internal static var hint: String { Localization.tr("Localizable", "call_visualizer.visitor_code.title.accessibility.hint", fallback: "Shows the five-digit visitor code.") } } } } } internal enum Chat { /// Pick attachment - internal static let attachFiles = Localization.tr("Localizable", "chat.attach_files", fallback: "Pick attachment") + internal static var attachFiles: String { Localization.tr("Localizable", "chat.attach_files", fallback: "Pick attachment") } /// New Messages - internal static let unreadMessageDivider = Localization.tr("Localizable", "chat.unread_message_divider", fallback: "New Messages") + internal static var unreadMessageDivider: String { Localization.tr("Localizable", "chat.unread_message_divider", fallback: "New Messages") } internal enum Attachment { /// Photo Library - internal static let photoLibrary = Localization.tr("Localizable", "chat.attachment.photo_library", fallback: "Photo Library") + internal static var photoLibrary: String { Localization.tr("Localizable", "chat.attachment.photo_library", fallback: "Photo Library") } /// Take Photo or Video - internal static let takePhoto = Localization.tr("Localizable", "chat.attachment.take_photo", fallback: "Take Photo or Video") + internal static var takePhoto: String { Localization.tr("Localizable", "chat.attachment.take_photo", fallback: "Take Photo or Video") } /// This file type is not supported. - internal static let unsupportedFile = Localization.tr("Localizable", "chat.attachment.unsupported_file", fallback: "This file type is not supported.") + internal static var unsupportedFile: String { Localization.tr("Localizable", "chat.attachment.unsupported_file", fallback: "This file type is not supported.") } internal enum Message { internal enum Accessibility { /// Attachment from {fileSender} - internal static let label = Localization.tr("Localizable", "chat.attachment.message.accessibility.label", fallback: "Attachment from {fileSender}") + internal static var label: String { Localization.tr("Localizable", "chat.attachment.message.accessibility.label", fallback: "Attachment from {fileSender}") } } } } internal enum ChoiceCard { /// Tap on the answer above - internal static let placeholderMessage = Localization.tr("Localizable", "chat.choice_card.placeholder_message", fallback: "Tap on the answer above") + internal static var placeholderMessage: String { Localization.tr("Localizable", "chat.choice_card.placeholder_message", fallback: "Tap on the answer above") } internal enum Button { internal enum Disabled { internal enum Accessibility { /// Disabled - internal static let label = Localization.tr("Localizable", "chat.choice_card.button.disabled.accessibility.label", fallback: "Disabled") + internal static var label: String { Localization.tr("Localizable", "chat.choice_card.button.disabled.accessibility.label", fallback: "Disabled") } } } } internal enum Image { internal enum Accessibility { /// Choice card - internal static let label = Localization.tr("Localizable", "chat.choice_card.image.accessibility.label", fallback: "Choice card") + internal static var label: String { Localization.tr("Localizable", "chat.choice_card.image.accessibility.label", fallback: "Choice card") } } } } internal enum Download { /// Downloading file… - internal static let downloading = Localization.tr("Localizable", "chat.download.downloading", fallback: "Downloading file…") + internal static var downloading: String { Localization.tr("Localizable", "chat.download.downloading", fallback: "Downloading file…") } /// Could not download the file. - internal static let failed = Localization.tr("Localizable", "chat.download.failed", fallback: "Could not download the file.") + internal static var failed: String { Localization.tr("Localizable", "chat.download.failed", fallback: "Could not download the file.") } } internal enum File { internal enum InfectedFile { /// The safety of the file could not be confirmed. - internal static let error = Localization.tr("Localizable", "chat.file.infected_file.error", fallback: "The safety of the file could not be confirmed.") + internal static var error: String { Localization.tr("Localizable", "chat.file.infected_file.error", fallback: "The safety of the file could not be confirmed.") } } internal enum RemoveUpload { internal enum Accessibility { /// Remove upload - internal static let label = Localization.tr("Localizable", "chat.file.remove_upload.accessibility.label", fallback: "Remove upload") + internal static var label: String { Localization.tr("Localizable", "chat.file.remove_upload.accessibility.label", fallback: "Remove upload") } } } internal enum SizeLimit { /// File size must be less than 25 MB. - internal static let error = Localization.tr("Localizable", "chat.file.size_limit.error", fallback: "File size must be less than 25 MB.") + internal static var error: String { Localization.tr("Localizable", "chat.file.size_limit.error", fallback: "File size must be less than 25 MB.") } } internal enum Upload { /// Could not upload the file. - internal static let failed = Localization.tr("Localizable", "chat.file.upload.failed", fallback: "Could not upload the file.") + internal static var failed: String { Localization.tr("Localizable", "chat.file.upload.failed", fallback: "Could not upload the file.") } /// Could not upload the file. - internal static let genericError = Localization.tr("Localizable", "chat.file.upload.generic_error", fallback: "Could not upload the file.") + internal static var genericError: String { Localization.tr("Localizable", "chat.file.upload.generic_error", fallback: "Could not upload the file.") } /// Uploading file… - internal static let inProgress = Localization.tr("Localizable", "chat.file.upload.in_progress", fallback: "Uploading file…") + internal static var inProgress: String { Localization.tr("Localizable", "chat.file.upload.in_progress", fallback: "Uploading file…") } /// Could not upload the file due to a network issue. - internal static let networkError = Localization.tr("Localizable", "chat.file.upload.network_error", fallback: "Could not upload the file due to a network issue.") + internal static var networkError: String { Localization.tr("Localizable", "chat.file.upload.network_error", fallback: "Could not upload the file due to a network issue.") } /// Checking file security… - internal static let scanning = Localization.tr("Localizable", "chat.file.upload.scanning", fallback: "Checking file security…") + internal static var scanning: String { Localization.tr("Localizable", "chat.file.upload.scanning", fallback: "Checking file security…") } /// Ready to send - internal static let success = Localization.tr("Localizable", "chat.file.upload.success", fallback: "Ready to send") + internal static var success: String { Localization.tr("Localizable", "chat.file.upload.success", fallback: "Ready to send") } } } internal enum Input { /// Enter Message - internal static let placeholder = Localization.tr("Localizable", "chat.input.placeholder", fallback: "Enter Message") + internal static var placeholder: String { Localization.tr("Localizable", "chat.input.placeholder", fallback: "Enter Message") } } internal enum MediaUpgrade { internal enum Audio { /// Upgraded to Audio - internal static let systemMessage = Localization.tr("Localizable", "chat.media_upgrade.audio.system_message", fallback: "Upgraded to Audio") + internal static var systemMessage: String { Localization.tr("Localizable", "chat.media_upgrade.audio.system_message", fallback: "Upgraded to Audio") } } internal enum Video { /// Upgraded to Video - internal static let systemMessage = Localization.tr("Localizable", "chat.media_upgrade.video.system_message", fallback: "Upgraded to Video") + internal static var systemMessage: String { Localization.tr("Localizable", "chat.media_upgrade.video.system_message", fallback: "Upgraded to Video") } } } internal enum Message { /// Delivered - internal static let delivered = Localization.tr("Localizable", "chat.message.delivered", fallback: "Delivered") + internal static var delivered: String { Localization.tr("Localizable", "chat.message.delivered", fallback: "Delivered") } /// Send a message to start chatting - internal static let startEngagementPlaceholder = Localization.tr("Localizable", "chat.message.start_engagement_placeholder", fallback: "Send a message to start chatting") + internal static var startEngagementPlaceholder: String { Localization.tr("Localizable", "chat.message.start_engagement_placeholder", fallback: "Send a message to start chatting") } internal enum Unread { internal enum Accessibility { /// Unread messages - internal static let label = Localization.tr("Localizable", "chat.message.unread.accessibility.label", fallback: "Unread messages") + internal static var label: String { Localization.tr("Localizable", "chat.message.unread.accessibility.label", fallback: "Unread messages") } } } } internal enum OperatorAvatar { internal enum Accessibility { /// Operator Picture - internal static let label = Localization.tr("Localizable", "chat.operator_avatar.accessibility.label", fallback: "Operator Picture") + internal static var label: String { Localization.tr("Localizable", "chat.operator_avatar.accessibility.label", fallback: "Operator Picture") } } } internal enum OperatorJoined { /// {operatorName} has joined the conversation. - internal static let systemMessage = Localization.tr("Localizable", "chat.operator_joined.system_message", fallback: "{operatorName} has joined the conversation.") + internal static var systemMessage: String { Localization.tr("Localizable", "chat.operator_joined.system_message", fallback: "{operatorName} has joined the conversation.") } } internal enum OperatorName { internal enum Accessibility { /// Operator Name - internal static let label = Localization.tr("Localizable", "chat.operator_name.accessibility.label", fallback: "Operator Name") + internal static var label: String { Localization.tr("Localizable", "chat.operator_name.accessibility.label", fallback: "Operator Name") } } } internal enum Status { /// Operator is typing - internal static let typing = Localization.tr("Localizable", "chat.status.typing", fallback: "Operator is typing") + internal static var typing: String { Localization.tr("Localizable", "chat.status.typing", fallback: "Operator is typing") } internal enum Typing { internal enum Accessibility { /// {operatorName} is typing - internal static let label = Localization.tr("Localizable", "chat.status.typing.accessibility.label", fallback: "{operatorName} is typing") + internal static var label: String { Localization.tr("Localizable", "chat.status.typing.accessibility.label", fallback: "{operatorName} is typing") } } } } } internal enum Engagement { /// Operator - internal static let defaultOperator = Localization.tr("Localizable", "engagement.default_operator", fallback: "Operator") + internal static var defaultOperator: String { Localization.tr("Localizable", "engagement.default_operator", fallback: "Operator") } internal enum Audio { /// Audio - internal static let title = Localization.tr("Localizable", "engagement.audio.title", fallback: "Audio") + internal static var title: String { Localization.tr("Localizable", "engagement.audio.title", fallback: "Audio") } } internal enum Chat { /// Chat - internal static let title = Localization.tr("Localizable", "engagement.chat.title", fallback: "Chat") + internal static var title: String { Localization.tr("Localizable", "engagement.chat.title", fallback: "Chat") } } internal enum ConnectionScreen { /// Connecting with {operatorName} - internal static let connectWith = Localization.tr("Localizable", "engagement.connection_screen.connect_with", fallback: "Connecting with {operatorName}") + internal static var connectWith: String { Localization.tr("Localizable", "engagement.connection_screen.connect_with", fallback: "Connecting with {operatorName}") } /// We are here to help! - internal static let message = Localization.tr("Localizable", "engagement.connection_screen.message", fallback: "We are here to help!") + internal static var message: String { Localization.tr("Localizable", "engagement.connection_screen.message", fallback: "We are here to help!") } } internal enum End { /// Are you sure you want to end this engagement? - internal static let message = Localization.tr("Localizable", "engagement.end.message", fallback: "Are you sure you want to end this engagement?") + internal static var message: String { Localization.tr("Localizable", "engagement.end.message", fallback: "Are you sure you want to end this engagement?") } internal enum Confirmation { /// End Engagement? - internal static let header = Localization.tr("Localizable", "engagement.end.confirmation.header", fallback: "End Engagement?") + internal static var header: String { Localization.tr("Localizable", "engagement.end.confirmation.header", fallback: "End Engagement?") } } } internal enum Ended { /// Engagement Ended - internal static let header = Localization.tr("Localizable", "engagement.ended.header", fallback: "Engagement Ended") + internal static var header: String { Localization.tr("Localizable", "engagement.ended.header", fallback: "Engagement Ended") } /// This engagement has ended. /// Thank you! - internal static let message = Localization.tr("Localizable", "engagement.ended.message", fallback: "This engagement has ended.\nThank you!") + internal static var message: String { Localization.tr("Localizable", "engagement.ended.message", fallback: "This engagement has ended.\nThank you!") } } internal enum MediaUpgrade { /// {operatorName} has offered you to upgrade. - internal static let offer = Localization.tr("Localizable", "engagement.media_upgrade.offer", fallback: "{operatorName} has offered you to upgrade.") + internal static var offer: String { Localization.tr("Localizable", "engagement.media_upgrade.offer", fallback: "{operatorName} has offered you to upgrade.") } internal enum Audio { /// Speak through your device - internal static let info = Localization.tr("Localizable", "engagement.media_upgrade.audio.info", fallback: "Speak through your device") + internal static var info: String { Localization.tr("Localizable", "engagement.media_upgrade.audio.info", fallback: "Speak through your device") } } internal enum Phone { /// Enter your number and will call you back. - internal static let info = Localization.tr("Localizable", "engagement.media_upgrade.phone.info", fallback: "Enter your number and will call you back.") + internal static var info: String { Localization.tr("Localizable", "engagement.media_upgrade.phone.info", fallback: "Enter your number and will call you back.") } } } internal enum MinimizeVideo { /// Minimize - internal static let button = Localization.tr("Localizable", "engagement.minimize_video.button", fallback: "Minimize") + internal static var button: String { Localization.tr("Localizable", "engagement.minimize_video.button", fallback: "Minimize") } } internal enum Phone { /// Phone - internal static let title = Localization.tr("Localizable", "engagement.phone.title", fallback: "Phone") + internal static var title: String { Localization.tr("Localizable", "engagement.phone.title", fallback: "Phone") } } internal enum Queue { /// Transferring - internal static let transferring = Localization.tr("Localizable", "engagement.queue.transferring", fallback: "Transferring") + internal static var transferring: String { Localization.tr("Localizable", "engagement.queue.transferring", fallback: "Transferring") } internal enum Closed { /// We are sorry! The queue is closed. - internal static let header = Localization.tr("Localizable", "engagement.queue.closed.header", fallback: "We are sorry! The queue is closed.") + internal static var header: String { Localization.tr("Localizable", "engagement.queue.closed.header", fallback: "We are sorry! The queue is closed.") } /// Operators are no longer available. /// Please try again later. - internal static let message = Localization.tr("Localizable", "engagement.queue.closed.message", fallback: "Operators are no longer available. \nPlease try again later.") + internal static var message: String { Localization.tr("Localizable", "engagement.queue.closed.message", fallback: "Operators are no longer available. \nPlease try again later.") } } internal enum Leave { /// Are you sure you want to leave? - internal static let header = Localization.tr("Localizable", "engagement.queue.leave.header", fallback: "Are you sure you want to leave?") + internal static var header: String { Localization.tr("Localizable", "engagement.queue.leave.header", fallback: "Are you sure you want to leave?") } /// You will lose your place in the queue. - internal static let message = Localization.tr("Localizable", "engagement.queue.leave.message", fallback: "You will lose your place in the queue.") + internal static var message: String { Localization.tr("Localizable", "engagement.queue.leave.message", fallback: "You will lose your place in the queue.") } } internal enum Reconnection { /// Please try again later. - internal static let failed = Localization.tr("Localizable", "engagement.queue.reconnection.failed", fallback: "Please try again later.") + internal static var failed: String { Localization.tr("Localizable", "engagement.queue.reconnection.failed", fallback: "Please try again later.") } } } internal enum QueueWait { /// You can continue browsing and we will connect you automatically. - internal static let message = Localization.tr("Localizable", "engagement.queue_wait.message", fallback: "You can continue browsing and we will connect you automatically.") + internal static var message: String { Localization.tr("Localizable", "engagement.queue_wait.message", fallback: "You can continue browsing and we will connect you automatically.") } } internal enum SecureMessaging { /// Messaging - internal static let title = Localization.tr("Localizable", "engagement.secure_messaging.title", fallback: "Messaging") + internal static var title: String { Localization.tr("Localizable", "engagement.secure_messaging.title", fallback: "Messaging") } } internal enum Video { /// Video - internal static let title = Localization.tr("Localizable", "engagement.video.title", fallback: "Video") + internal static var title: String { Localization.tr("Localizable", "engagement.video.title", fallback: "Video") } } } internal enum Error { /// Something went wrong. - internal static let general = Localization.tr("Localizable", "error.general", fallback: "Something went wrong.") + internal static var general: String { Localization.tr("Localizable", "error.general", fallback: "Something went wrong.") } /// Something went wrong. - internal static let `internal` = Localization.tr("Localizable", "error.internal", fallback: "Something went wrong.") + internal static var `internal`: String { Localization.tr("Localizable", "error.internal", fallback: "Something went wrong.") } /// Something went wrong. - internal static let unexpected = Localization.tr("Localizable", "error.unexpected", fallback: "Something went wrong.") + internal static var unexpected: String { Localization.tr("Localizable", "error.unexpected", fallback: "Something went wrong.") } } internal enum General { /// Accept - internal static let accept = Localization.tr("Localizable", "general.accept", fallback: "Accept") + internal static var accept: String { Localization.tr("Localizable", "general.accept", fallback: "Accept") } /// Back - internal static let back = Localization.tr("Localizable", "general.back", fallback: "Back") + internal static var back: String { Localization.tr("Localizable", "general.back", fallback: "Back") } /// Browse - internal static let browse = Localization.tr("Localizable", "general.browse", fallback: "Browse") + internal static var browse: String { Localization.tr("Localizable", "general.browse", fallback: "Browse") } /// Cancel - internal static let cancel = Localization.tr("Localizable", "general.cancel", fallback: "Cancel") + internal static var cancel: String { Localization.tr("Localizable", "general.cancel", fallback: "Cancel") } /// Close - internal static let close = Localization.tr("Localizable", "general.close", fallback: "Close") + internal static var close: String { Localization.tr("Localizable", "general.close", fallback: "Close") } /// Comment - internal static let comment = Localization.tr("Localizable", "general.comment", fallback: "Comment") + internal static var comment: String { Localization.tr("Localizable", "general.comment", fallback: "Comment") } /// Company Name - internal static let companyName = Localization.tr("Localizable", "general.company_name", fallback: "Company Name") + internal static var companyName: String { Localization.tr("Localizable", "general.company_name", fallback: "Company Name") } /// Company Name without asking string provider - internal static let companyNameLocalFallbackOnly = Localization.tr("Localizable", "general.company_name", fallback: "Company Name", stringProviding: nil) + internal static var companyNameLocalFallbackOnly: String { Localization.tr("Localizable", "general.company_name", fallback: "Company Name", stringProviding: nil) } /// Decline - internal static let decline = Localization.tr("Localizable", "general.decline", fallback: "Decline") + internal static var decline: String { Localization.tr("Localizable", "general.decline", fallback: "Decline") } /// Download - internal static let download = Localization.tr("Localizable", "general.download", fallback: "Download") + internal static var download: String { Localization.tr("Localizable", "general.download", fallback: "Download") } /// End - internal static let end = Localization.tr("Localizable", "general.end", fallback: "End") + internal static var end: String { Localization.tr("Localizable", "general.end", fallback: "End") } /// Message - internal static let message = Localization.tr("Localizable", "general.message", fallback: "Message") + internal static var message: String { Localization.tr("Localizable", "general.message", fallback: "Message") } /// No - internal static let no = Localization.tr("Localizable", "general.no", fallback: "No") + internal static var no: String { Localization.tr("Localizable", "general.no", fallback: "No") } /// Ok - internal static let ok = Localization.tr("Localizable", "general.ok", fallback: "Ok") + internal static var ok: String { Localization.tr("Localizable", "general.ok", fallback: "Ok") } /// Open - internal static let `open` = Localization.tr("Localizable", "general.open", fallback: "Open") + internal static var `open`: String { Localization.tr("Localizable", "general.open", fallback: "Open") } /// Powered by - internal static let powered = Localization.tr("Localizable", "general.powered", fallback: "Powered by") + internal static var powered: String { Localization.tr("Localizable", "general.powered", fallback: "Powered by") } /// Refresh - internal static let refresh = Localization.tr("Localizable", "general.refresh", fallback: "Refresh") + internal static var refresh: String { Localization.tr("Localizable", "general.refresh", fallback: "Refresh") } /// Retry - internal static let retry = Localization.tr("Localizable", "general.retry", fallback: "Retry") + internal static var retry: String { Localization.tr("Localizable", "general.retry", fallback: "Retry") } /// Selected - internal static let selected = Localization.tr("Localizable", "general.selected", fallback: "Selected") + internal static var selected: String { Localization.tr("Localizable", "general.selected", fallback: "Selected") } /// Send - internal static let send = Localization.tr("Localizable", "general.send", fallback: "Send") + internal static var send: String { Localization.tr("Localizable", "general.send", fallback: "Send") } /// Sending… - internal static let sending = Localization.tr("Localizable", "general.sending", fallback: "Sending…") + internal static var sending: String { Localization.tr("Localizable", "general.sending", fallback: "Sending…") } /// Submit - internal static let submit = Localization.tr("Localizable", "general.submit", fallback: "Submit") + internal static var submit: String { Localization.tr("Localizable", "general.submit", fallback: "Submit") } /// Thank you! - internal static let thankYou = Localization.tr("Localizable", "general.thank_you", fallback: "Thank you!") + internal static var thankYou: String { Localization.tr("Localizable", "general.thank_you", fallback: "Thank you!") } /// Yes - internal static let yes = Localization.tr("Localizable", "general.yes", fallback: "Yes") + internal static var yes: String { Localization.tr("Localizable", "general.yes", fallback: "Yes") } /// You - internal static let you = Localization.tr("Localizable", "general.you", fallback: "You") + internal static var you: String { Localization.tr("Localizable", "general.you", fallback: "You") } internal enum Close { /// Close Button - internal static let accessibility = Localization.tr("Localizable", "general.close.accessibility", fallback: "Close Button") + internal static var accessibility: String { Localization.tr("Localizable", "general.close.accessibility", fallback: "Close Button") } } } internal enum Gva { internal enum UnsupportedAction { /// This action is not currently supported on mobile. - internal static let error = Localization.tr("Localizable", "gva.unsupported_action.error", fallback: "This action is not currently supported on mobile.") + internal static var error: String { Localization.tr("Localizable", "gva.unsupported_action.error", fallback: "This action is not currently supported on mobile.") } } } internal enum Ios { internal enum Alert { internal enum CameraAccess { /// Allow access to your camera in 'Settings' - 'Privacy & Security' - 'Camera' - internal static let message = Localization.tr("Localizable", "ios.alert.camera_access.message", fallback: "Allow access to your camera in 'Settings' - 'Privacy & Security' - 'Camera'") + internal static var message: String { Localization.tr("Localizable", "ios.alert.camera_access.message", fallback: "Allow access to your camera in 'Settings' - 'Privacy & Security' - 'Camera'") } } internal enum MediaSource { /// This media source is not available on your device - internal static let message = Localization.tr("Localizable", "ios.alert.media_source.message", fallback: "This media source is not available on your device") + internal static var message: String { Localization.tr("Localizable", "ios.alert.media_source.message", fallback: "This media source is not available on your device") } } internal enum MicrophoneAccess { /// Allow access to your microphone in 'Settings' - 'Privacy & Security' - 'Microphone' - internal static let message = Localization.tr("Localizable", "ios.alert.microphone_access.message", fallback: "Allow access to your microphone in 'Settings' - 'Privacy & Security' - 'Microphone'") + internal static var message: String { Localization.tr("Localizable", "ios.alert.microphone_access.message", fallback: "Allow access to your microphone in 'Settings' - 'Privacy & Security' - 'Microphone'") } } } internal enum Engagement { internal enum ConnectionScreen { /// (By default, your video will be turned off) - internal static let videoNotice = Localization.tr("Localizable", "ios.engagement.connection_screen.video_notice", fallback: "(By default, your video will be turned off)") + internal static var videoNotice: String { Localization.tr("Localizable", "ios.engagement.connection_screen.video_notice", fallback: "(By default, your video will be turned off)") } } } } internal enum MediaUpgrade { internal enum Audio { /// {operatorName} has offered you to upgrade to audio - internal static let title = Localization.tr("Localizable", "media_upgrade.audio.title", fallback: "{operatorName} has offered you to upgrade to audio") + internal static var title: String { Localization.tr("Localizable", "media_upgrade.audio.title", fallback: "{operatorName} has offered you to upgrade to audio") } } internal enum Video { internal enum OneWay { /// {operatorName} has offered you to see their video - internal static let title = Localization.tr("Localizable", "media_upgrade.video.one_way.title", fallback: "{operatorName} has offered you to see their video") + internal static var title: String { Localization.tr("Localizable", "media_upgrade.video.one_way.title", fallback: "{operatorName} has offered you to see their video") } } internal enum TwoWay { /// {operatorName} has offered you to upgrade to video - internal static let title = Localization.tr("Localizable", "media_upgrade.video.two_way.title", fallback: "{operatorName} has offered you to upgrade to video") + internal static var title: String { Localization.tr("Localizable", "media_upgrade.video.two_way.title", fallback: "{operatorName} has offered you to upgrade to video") } } } } internal enum MessageCenter { /// Messaging - internal static let header = Localization.tr("Localizable", "message_center.header", fallback: "Messaging") + internal static var header: String { Localization.tr("Localizable", "message_center.header", fallback: "Messaging") } internal enum Confirmation { /// Your message has been sent. We will get back to you within 48 hours. - internal static let subtitle = Localization.tr("Localizable", "message_center.confirmation.subtitle", fallback: "Your message has been sent. We will get back to you within 48 hours.") + internal static var subtitle: String { Localization.tr("Localizable", "message_center.confirmation.subtitle", fallback: "Your message has been sent. We will get back to you within 48 hours.") } internal enum CheckMessages { internal enum Accessibility { /// Navigates you to the chat transcript. - internal static let hint = Localization.tr("Localizable", "message_center.confirmation.check_messages.accessibility.hint", fallback: "Navigates you to the chat transcript.") + internal static var hint: String { Localization.tr("Localizable", "message_center.confirmation.check_messages.accessibility.hint", fallback: "Navigates you to the chat transcript.") } /// Check messages - internal static let label = Localization.tr("Localizable", "message_center.confirmation.check_messages.accessibility.label", fallback: "Check messages") + internal static var label: String { Localization.tr("Localizable", "message_center.confirmation.check_messages.accessibility.label", fallback: "Check messages") } } } } internal enum NotAuthenticated { /// We could not verify your authentication status. - internal static let message = Localization.tr("Localizable", "message_center.not_authenticated.message", fallback: "We could not verify your authentication status.") + internal static var message: String { Localization.tr("Localizable", "message_center.not_authenticated.message", fallback: "We could not verify your authentication status.") } } internal enum Unavailable { /// The Message Center is currently unavailable. Please try again later. - internal static let message = Localization.tr("Localizable", "message_center.unavailable.message", fallback: "The Message Center is currently unavailable. Please try again later.") + internal static var message: String { Localization.tr("Localizable", "message_center.unavailable.message", fallback: "The Message Center is currently unavailable. Please try again later.") } /// Message Center Unavailable - internal static let title = Localization.tr("Localizable", "message_center.unavailable.title", fallback: "Message Center Unavailable") + internal static var title: String { Localization.tr("Localizable", "message_center.unavailable.title", fallback: "Message Center Unavailable") } } internal enum Welcome { /// Check messages - internal static let checkMessages = Localization.tr("Localizable", "message_center.welcome.check_messages", fallback: "Check messages") + internal static var checkMessages: String { Localization.tr("Localizable", "message_center.welcome.check_messages", fallback: "Check messages") } /// Your message - internal static let messageTitle = Localization.tr("Localizable", "message_center.welcome.message_title", fallback: "Your message") + internal static var messageTitle: String { Localization.tr("Localizable", "message_center.welcome.message_title", fallback: "Your message") } /// Send a message and we will get back to you within 48 hours. - internal static let subtitle = Localization.tr("Localizable", "message_center.welcome.subtitle", fallback: "Send a message and we will get back to you within 48 hours.") + internal static var subtitle: String { Localization.tr("Localizable", "message_center.welcome.subtitle", fallback: "Send a message and we will get back to you within 48 hours.") } /// Welcome to Message Center - internal static let title = Localization.tr("Localizable", "message_center.welcome.title", fallback: "Welcome to Message Center") + internal static var title: String { Localization.tr("Localizable", "message_center.welcome.title", fallback: "Welcome to Message Center") } internal enum CheckMessages { internal enum Accessibility { /// Navigates you to the chat transcript. - internal static let hint = Localization.tr("Localizable", "message_center.welcome.check_messages.accessibility.hint", fallback: "Navigates you to the chat transcript.") + internal static var hint: String { Localization.tr("Localizable", "message_center.welcome.check_messages.accessibility.hint", fallback: "Navigates you to the chat transcript.") } } } internal enum FilePicker { internal enum Accessibility { /// Opens the file picker to attach media. - internal static let hint = Localization.tr("Localizable", "message_center.welcome.file_picker.accessibility.hint", fallback: "Opens the file picker to attach media.") + internal static var hint: String { Localization.tr("Localizable", "message_center.welcome.file_picker.accessibility.hint", fallback: "Opens the file picker to attach media.") } /// File picker - internal static let label = Localization.tr("Localizable", "message_center.welcome.file_picker.accessibility.label", fallback: "File picker") + internal static var label: String { Localization.tr("Localizable", "message_center.welcome.file_picker.accessibility.label", fallback: "File picker") } } } internal enum MessageInput { /// Enter your message - internal static let placeholder = Localization.tr("Localizable", "message_center.welcome.message_input.placeholder", fallback: "Enter your message") + internal static var placeholder: String { Localization.tr("Localizable", "message_center.welcome.message_input.placeholder", fallback: "Enter your message") } } internal enum MessageLength { /// The message cannot exceed 10,000 characters. - internal static let error = Localization.tr("Localizable", "message_center.welcome.message_length.error", fallback: "The message cannot exceed 10,000 characters.") + internal static var error: String { Localization.tr("Localizable", "message_center.welcome.message_length.error", fallback: "The message cannot exceed 10,000 characters.") } } internal enum Send { internal enum Accessibility { /// Sends a secure message. - internal static let hint = Localization.tr("Localizable", "message_center.welcome.send.accessibility.hint", fallback: "Sends a secure message.") + internal static var hint: String { Localization.tr("Localizable", "message_center.welcome.send.accessibility.hint", fallback: "Sends a secure message.") } } } } @@ -566,16 +566,16 @@ internal enum Localization { internal enum VisitorScreen { internal enum Disclaimer { /// Depending on your selection, your entire screen might be shared with the operator, not just the application window. - internal static let info = Localization.tr("Localizable", "screen_sharing.visitor_screen.disclaimer.info", fallback: "Depending on your selection, your entire screen might be shared with the operator, not just the application window.") + internal static var info: String { Localization.tr("Localizable", "screen_sharing.visitor_screen.disclaimer.info", fallback: "Depending on your selection, your entire screen might be shared with the operator, not just the application window.") } /// You are about to share your screen - internal static let title = Localization.tr("Localizable", "screen_sharing.visitor_screen.disclaimer.title", fallback: "You are about to share your screen") + internal static var title: String { Localization.tr("Localizable", "screen_sharing.visitor_screen.disclaimer.title", fallback: "You are about to share your screen") } } internal enum End { /// End Screen Sharing - internal static let title = Localization.tr("Localizable", "screen_sharing.visitor_screen.end.title", fallback: "End Screen Sharing") + internal static var title: String { Localization.tr("Localizable", "screen_sharing.visitor_screen.end.title", fallback: "End Screen Sharing") } internal enum Accessibility { /// Ends screen sharing - internal static let hint = Localization.tr("Localizable", "screen_sharing.visitor_screen.end.accessibility.hint", fallback: "Ends screen sharing") + internal static var hint: String { Localization.tr("Localizable", "screen_sharing.visitor_screen.end.accessibility.hint", fallback: "Ends screen sharing") } } } } @@ -583,33 +583,33 @@ internal enum Localization { internal enum Survey { internal enum Action { /// Please provide an answer. - internal static let validationError = Localization.tr("Localizable", "survey.action.validation_error", fallback: "Please provide an answer.") + internal static var validationError: String { Localization.tr("Localizable", "survey.action.validation_error", fallback: "Please provide an answer.") } } internal enum Question { internal enum Input { internal enum Accessibility { /// Enter the answer - internal static let hint = Localization.tr("Localizable", "survey.question.input.accessibility.hint", fallback: "Enter the answer") + internal static var hint: String { Localization.tr("Localizable", "survey.question.input.accessibility.hint", fallback: "Enter the answer") } } } internal enum OptionButton { internal enum Selected { internal enum Accessibility { /// Selected: {buttonTitle} - internal static let label = Localization.tr("Localizable", "survey.question.option_button.selected.accessibility.label", fallback: "Selected: {buttonTitle}") + internal static var label: String { Localization.tr("Localizable", "survey.question.option_button.selected.accessibility.label", fallback: "Selected: {buttonTitle}") } } } internal enum Unselected { internal enum Accessibility { /// Unselected: {buttonTitle} - internal static let label = Localization.tr("Localizable", "survey.question.option_button.unselected.accessibility.label", fallback: "Unselected: {buttonTitle}") + internal static var label: String { Localization.tr("Localizable", "survey.question.option_button.unselected.accessibility.label", fallback: "Unselected: {buttonTitle}") } } } } internal enum Required { internal enum Accessibility { /// This is a required question. - internal static let label = Localization.tr("Localizable", "survey.question.required.accessibility.label", fallback: "This is a required question.") + internal static var label: String { Localization.tr("Localizable", "survey.question.required.accessibility.label", fallback: "This is a required question.") } } } } @@ -617,14 +617,14 @@ internal enum Localization { internal enum Title { internal enum Accessibility { /// Please provide an answer for the question above. - internal static let label = Localization.tr("Localizable", "survey.validation.title.accessibility.label", fallback: "Please provide an answer for the question above.") + internal static var label: String { Localization.tr("Localizable", "survey.validation.title.accessibility.label", fallback: "Please provide an answer for the question above.") } } } } } internal enum VisitorCode { /// Could not load the visitor code. Please try refreshing. - internal static let failed = Localization.tr("Localizable", "visitor_code.failed", fallback: "Could not load the visitor code. Please try refreshing.") + internal static var failed: String { Localization.tr("Localizable", "visitor_code.failed", fallback: "Could not load the visitor code. Please try refreshing.") } } } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length diff --git a/GliaWidgets/Public/Configuration/Configuration+Mock.swift b/GliaWidgets/Public/Configuration/Configuration+Mock.swift index 409453c4a..c1956ed95 100644 --- a/GliaWidgets/Public/Configuration/Configuration+Mock.swift +++ b/GliaWidgets/Public/Configuration/Configuration+Mock.swift @@ -8,13 +8,15 @@ extension Configuration { authMethod: AuthorizationMethod = .siteApiKey(id: "site-api-key-id", secret: "site-api-key-secret"), environment: Environment = .beta, site: String = "site-id", - companyName: String = "" + companyName: String = "", + manualLocaleOverride: String? = nil ) -> Self { Configuration( authorizationMethod: authMethod, environment: environment, site: site, - companyName: companyName + companyName: companyName, + manualLocaleOverride: manualLocaleOverride ) } } diff --git a/GliaWidgets/Public/Configuration/Configuration.swift b/GliaWidgets/Public/Configuration/Configuration.swift index 84f688404..15f110eec 100644 --- a/GliaWidgets/Public/Configuration/Configuration.swift +++ b/GliaWidgets/Public/Configuration/Configuration.swift @@ -17,6 +17,9 @@ public struct Configuration { public let isWhiteLabelApp: Bool /// Company name. Appears during connection with operator. public let companyName: String + /// The name of the manual locale override. If not set, or if set as `nil`, + /// then the default locale from site settings will be used. + public var manualLocaleOverride: String? /// Initializes the configuration. /// /// - Parameters: @@ -24,7 +27,8 @@ public struct Configuration { /// - environment: The environment to use. /// - site: The site to use. /// - visitorContext: Additional context about the visitor that operator may need. - /// + /// - manualLocaleOverride: The name of the manual locale override. + /// If not set, or if set as `nil`, then the default locale from site settings will be used. public init( authorizationMethod: AuthorizationMethod, environment: Environment, @@ -32,7 +36,8 @@ public struct Configuration { visitorContext: VisitorContext? = nil, pushNotifications: PushNotifications = .disabled, isWhiteLabelApp: Bool = false, - companyName: String = "" + companyName: String = "", + manualLocaleOverride: String? = nil ) { self.authorizationMethod = authorizationMethod self.environment = environment @@ -41,6 +46,7 @@ public struct Configuration { self.pushNotifications = pushNotifications self.isWhiteLabelApp = isWhiteLabelApp self.companyName = companyName + self.manualLocaleOverride = manualLocaleOverride } } diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index f0ec3cbe8..a34c951b8 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -149,9 +149,23 @@ public class Glia { } completion(.success(())) - case let .failure(error): + case .failure(let error): + typealias ProcessError = CoreSdkClient.ConfigurationProcessError + var errorForCompletion: GliaError = .internalError + + // To avoid the integrator having to figure out if an error is a `GliaError` + // or a `ConfigurationProcessError`, the `ConfigurationProcessError` is translated + // into a `GliaError`. + if let processError = error as? ProcessError { + if processError == .invalidSiteApiKeyCredentials { + errorForCompletion = GliaError.invalidSiteApiKeyCredentials + } else if processError == .localeRetrieval { + errorForCompletion = GliaError.invalidLocale + } + } + debugPrint("💥 Core SDK configuration is not valid. Unexpected error='\(error)'.") - completion(.failure(error)) + completion(.failure(errorForCompletion)) } } } diff --git a/GliaWidgets/Public/GliaError.swift b/GliaWidgets/Public/GliaError.swift index ee9349849..e651b77ab 100644 --- a/GliaWidgets/Public/GliaError.swift +++ b/GliaWidgets/Public/GliaError.swift @@ -7,5 +7,7 @@ public enum GliaError: Error { case configuringDuringEngagementIsNotAllowed case clearingVisitorSessionDuringEngagementIsNotAllowed case startingEngagementWithNoQueueIdsIsNotAllowed + case invalidSiteApiKeyCredentials + case invalidLocale case internalError } diff --git a/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.swift b/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.swift index 1dc4d1170..e92c5b29d 100644 --- a/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.swift +++ b/GliaWidgets/Sources/Coordinators/Chat/ChatCoordinator.swift @@ -78,7 +78,6 @@ class ChatCoordinator: SubFlowCoordinator, FlowCoordinator { ) self.controller = chatController return chatController - } private func presentMediaPickerController( diff --git a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift index d734c0285..19c5a279b 100644 --- a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift +++ b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift @@ -198,6 +198,7 @@ extension CoreSdkClient { typealias FileError = GliaCoreSDK.FileError typealias GeneralError = GliaCoreSDK.GeneralError typealias GliaCoreError = GliaCoreSDK.GliaCoreError + typealias ConfigurationProcessError = GliaCoreSDK.GliaCore.ConfigurationProcessError typealias Interactable = GliaCoreSDK.Interactable typealias MediaDirection = GliaCoreSDK.MediaDirection typealias MediaError = GliaCoreSDK.MediaError diff --git a/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift index 3fa83ef96..6613b4eb2 100644 --- a/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift +++ b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift @@ -14,7 +14,8 @@ extension CoreSDKConfigurator { siteId: configuration.site, region: configuration.environment.region, authorizingMethod: configuration.authorizationMethod.coreAuthorizationMethod, - pushNotifications: configuration.pushNotifications.coreSdk + pushNotifications: configuration.pushNotifications.coreSdk, + manualLocaleOverride: configuration.manualLocaleOverride ) coreSdk.configureWithConfiguration(sdkConfiguration, completion) } diff --git a/TestingApp/Extensions/Configuration.Extensions.swift b/TestingApp/Extensions/Configuration.Extensions.swift index eb5c29634..25d79e252 100644 --- a/TestingApp/Extensions/Configuration.Extensions.swift +++ b/TestingApp/Extensions/Configuration.Extensions.swift @@ -8,7 +8,8 @@ extension Configuration { .init( authorizationMethod: .siteApiKey(id: "", secret: ""), environment: env, - site: "" + site: "", + manualLocaleOverride: nil ) } } @@ -25,15 +26,17 @@ extension Configuration { return nil } - let visitorAssetId = queryItems.first(where: { $0.name == "visitor_context_asset_id" })?.value ?? "" + let visitorAssetId = queryItems.first { $0.name == "visitor_context_asset_id" }?.value ?? "" let visitorContext = UUID(uuidString: visitorAssetId) .map(Configuration.VisitorContext.init(assetId:)) + let manualLocaleOverride = queryItems.first { $0.name == "manual_locale_override" }?.value ?? nil self = .init( authorizationMethod: .siteApiKey(id: siteApiKeyId, secret: siteApiKeySecret), environment: environment, site: siteId, - visitorContext: visitorContext + visitorContext: visitorContext, + manualLocaleOverride: manualLocaleOverride ) } } @@ -56,7 +59,12 @@ private extension Environment { extension Configuration: Codable { enum CodingKeys: String, CodingKey { - case authorizationMethod, environment, site, visitorContext, pushNotifications + case authorizationMethod, + environment, + site, + visitorContext, + pushNotifications, + manualLocaleOverride } public init(from decoder: Decoder) throws { @@ -66,7 +74,8 @@ extension Configuration: Codable { environment: container.decode(Environment.self, forKey: .environment), site: container.decode(String.self, forKey: .site), visitorContext: container.decodeIfPresent(VisitorContext.self, forKey: .visitorContext), - pushNotifications: container.decodeIfPresent(PushNotifications.self, forKey: .pushNotifications) ?? .disabled + pushNotifications: container.decodeIfPresent(PushNotifications.self, forKey: .pushNotifications) ?? .disabled, + manualLocaleOverride: container.decodeIfPresent(String.self, forKey: .manualLocaleOverride) ?? nil ) } @@ -77,6 +86,7 @@ extension Configuration: Codable { try container.encode(site, forKey: .site) try container.encode(visitorContext, forKey: .visitorContext) try container.encode(pushNotifications, forKey: .pushNotifications) + try container.encode(manualLocaleOverride, forKey: .manualLocaleOverride) } } @@ -163,6 +173,8 @@ extension Configuration.PushNotifications: RawRepresentable, Codable { return "sandbox" case .production: return "production" + @unknown default: + return "" } } diff --git a/TestingApp/Settings/SettingsViewController.swift b/TestingApp/Settings/SettingsViewController.swift index f09d123ac..141c1c227 100644 --- a/TestingApp/Settings/SettingsViewController.swift +++ b/TestingApp/Settings/SettingsViewController.swift @@ -15,6 +15,7 @@ final class SettingsViewController: UIViewController { private var queueIDCell: SettingsTextCell! private var environmentCell: EnvironmentSettingsTextCell! private var visitorContextAssedIdCell: SettingsTextCell! + private var manualLocaleOverrideCell: SettingsTextCell! private var bubbleFeatureCell: SettingsSwitchCell! private var primaryColorCell: SettingsColorCell! private var secondaryColorCell: SettingsColorCell! @@ -151,6 +152,13 @@ private extension SettingsViewController { text: props.config.visitorContext?.assetId.uuidString ?? "" ) visitorContextAssedIdCell.textField.accessibilityIdentifier = "settings_visitor_context_assetId_textfield" + + manualLocaleOverrideCell = SettingsTextCell( + title: "Manual locale override:", + text: props.config.manualLocaleOverride ?? "" + ) + manualLocaleOverrideCell.textField.accessibilityIdentifier = "settings_manual_locale_override_textfield" + bubbleFeatureCell = SettingsSwitchCell( title: "Present \"Bubble\" overlay in engagement time", isOn: props.features ~= .bubbleView @@ -272,7 +280,8 @@ private extension SettingsViewController { siteApiKeyIdCell, siteApiKeySecretCell, queueIDCell, - visitorContextAssedIdCell + visitorContextAssedIdCell, + manualLocaleOverrideCell ] configurationSection = Section( title: "Glia configuration", @@ -284,12 +293,16 @@ private extension SettingsViewController { private func updateConfiguration() { let uuid = UUID(uuidString: visitorContextAssedIdCell.textField.text ?? "") + let manualLocaleOverrideText = manualLocaleOverrideCell.textField.text ?? "" + let manualLocaleOverride = manualLocaleOverrideText.isEmpty ? nil : manualLocaleOverrideText + props.changeConfig( Configuration( authorizationMethod: siteApiKey, environment: environmentCell.environment, site: siteCell.textField.text ?? "", - visitorContext: uuid.map { Configuration.VisitorContext(assetId: $0) } + visitorContext: uuid.map { Configuration.VisitorContext(assetId: $0) }, + manualLocaleOverride: manualLocaleOverride ) ) diff --git a/TestingApp/ViewController/ViewController.swift b/TestingApp/ViewController/ViewController.swift index e90b66357..09f0b36d5 100644 --- a/TestingApp/ViewController/ViewController.swift +++ b/TestingApp/ViewController/ViewController.swift @@ -91,7 +91,6 @@ class ViewController: UIViewController { private func showErrorAlert(using error: Error) { if let gliaError = error as? GliaError { - switch gliaError { case GliaError.engagementExists: alert(message: "Failed to start\nEngagement is ongoing, please use 'Resume' button") @@ -101,6 +100,10 @@ class ViewController: UIViewController { alert(message: "Failed to start\nCall Visualizer engagement is ongoing") case GliaError.configuringDuringEngagementIsNotAllowed: alert(message: "The operation couldn't be completed. '\(gliaError)'.") + case GliaError.invalidSiteApiKeyCredentials: + alert(message: "Failed to configure the SDK, invalid credentials") + case GliaError.invalidLocale: + alert(message: "Failed to configure the SDK, invalid locale override specified") default: alert(message: "Failed to start\nCheck Glia parameters in Settings") } @@ -240,6 +243,7 @@ extension ViewController { self?.configureButton.accessibilityIdentifier = originalIdentifier debugPrint(printable) } + do { try Glia.sharedInstance.configure( with: configuration @@ -249,7 +253,7 @@ extension ViewController { completionBlock("SDK has been configured") completion?(.success(())) case let .failure(error): - completionBlock(error) + completionBlock("Error configuring the SDK") completion?(.failure(error)) } } @@ -373,7 +377,6 @@ extension ViewController { } func setupPushHandler() { - let waitForActiveEngagement = GliaCore.sharedInstance.waitForActiveEngagement(completion:) GliaCore.sharedInstance.pushNotifications.handler = { [weak self] push in switch (push.type, push.timing) { // Open chat transcript only when the push notification has come @@ -403,19 +406,26 @@ extension ViewController { @IBAction private func toggleAuthentication() { catchingError { - try Glia.sharedInstance.configure(with: configuration) { [weak self] in + try Glia.sharedInstance.configure(with: configuration) { [weak self] result in guard let self = self else { return } - self.catchingError { - let authentication = try Self.authentication() - switch authentication.isAuthenticated { - case false: - self.showAuthorize(with: authentication) - case true: - self.showDeauthorize( - authorization: authentication, - from: self.toggleAuthenticateButton - ) + + switch result { + case .success: + self.catchingError { + let authentication = try Self.authentication() + switch authentication.isAuthenticated { + case false: + self.showAuthorize(with: authentication) + case true: + self.showDeauthorize( + authorization: authentication, + from: self.toggleAuthenticateButton + ) + } } + + case .failure(let error): + self.showErrorAlert(using: error) } } } diff --git a/swiftgen-strings.stencil b/swiftgen-strings.stencil index db9773940..d1cc07106 100644 --- a/swiftgen-strings.stencil +++ b/swiftgen-strings.stencil @@ -54,10 +54,10 @@ import Foundation {% elif param.lookupFunction %} {{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}", fallback: "{{translation}}") } {% else %} - {{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}", fallback: "{{translation}}") + {{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { {{enumName}}.tr("{{table}}", "{{string.key}}", fallback: "{{translation}}") } {% if string.key == "general.company_name" %} /// {{translation}} without asking string provider - {{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}LocalFallbackOnly = {{enumName}}.tr("{{table}}", "{{string.key}}", fallback: "{{translation}}", stringProviding: nil) + {{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}LocalFallbackOnly: String { {{enumName}}.tr("{{table}}", "{{string.key}}", fallback: "{{translation}}", stringProviding: nil) } {% endif %} {% endif %} {% endfor %}