diff --git a/GliaWidgets.podspec b/GliaWidgets.podspec index d4fffcf2d..433b49008 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' @@ -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/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index 12fa694e6..18d20f15e 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -349,6 +349,9 @@ 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 */; }; + 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 */; }; @@ -370,6 +373,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 +580,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 */; }; @@ -1085,6 +1092,9 @@ 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 = ""; }; + 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 = ""; }; @@ -1106,6 +1116,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 +1327,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 +1721,7 @@ 9A3E1D9C27BA7741005634EB /* FoundationBased.Failing.swift */, 84D5B9652A15204400807F92 /* QuickLookBased.Failing.swift */, 9A1992E627D66C7400161AAE /* UIKitBased.Failing.swift */, + AFF9542B2ADDA10600C277E0 /* CoreSDKConfigurator.Failing.swift */, ); path = GliaWidgetsTests; sourceTree = ""; @@ -2065,6 +2080,7 @@ 1A60AFC62566865F00E53F53 /* Coordinator */, 1A60AF6825656C0200E53F53 /* Coordinators */, 75940940298D378A008B173A /* CoreSDKClient */, + 847956342AD96AA8004EF60C /* CoreSDKConfigurator */, 1A1E309425F8CA3400850E68 /* Download */, 1A60B025256806D500E53F53 /* Extensions */, 1A8366AE25FF409B005FE7EE /* File */, @@ -2660,6 +2676,7 @@ 7512A57827BF9FB800319DF1 /* Sources */ = { isa = PBXGroup; children = ( + 84602A752AEA5BDE0031E606 /* ProximityManager */, 84681A932A61840700DD7406 /* ChatViewModel */, 846429812A45DA5900943BD6 /* AlertViewController */, 846A5C4329F6BEB60049B29F /* Glia */, @@ -2887,6 +2904,7 @@ 7594096A298D38C2008B173A /* CallVisualizer.BubbleIcon.swift */, 7594097B298D38C2008B173A /* CallVisualizer+Environment.swift */, 7594097C298D38C2008B173A /* CallVisualizer.swift */, + 8479563F2ADED7A2004EF60C /* CallVisualizer+Action.swift */, ); path = CallVisualizer; sourceTree = ""; @@ -3106,6 +3124,14 @@ path = URLScheme; sourceTree = ""; }; + 84602A752AEA5BDE0031E606 /* ProximityManager */ = { + isa = PBXGroup; + children = ( + 84602A762AEA5BEA0031E606 /* ProximityManager.Failing.swift */, + ); + path = ProximityManager; + sourceTree = ""; + }; 8464297B2A459E3500943BD6 /* Replaceble */ = { isa = PBXGroup; children = ( @@ -3156,6 +3182,7 @@ isa = PBXGroup; children = ( 84681A972A61853300DD7406 /* GvaOption.Mock.swift */, + 84602A782AEAB7CA0031E606 /* Survey.Mock.swift */, ); path = Mocks; sourceTree = ""; @@ -3204,6 +3231,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 = ( @@ -3504,6 +3540,7 @@ isa = PBXGroup; children = ( C05E3EDD29C99E070013BC81 /* ProximityManager.swift */, + 84602A732AE94DE50031E606 /* ProximityManager.Mock.swift */, ); path = ProximityManager; sourceTree = ""; @@ -4611,6 +4648,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 */, @@ -4666,6 +4704,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 +4734,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 */, @@ -4834,6 +4875,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 */, @@ -4893,6 +4935,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 */, @@ -4900,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/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/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+StartEngagement.swift b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift index dc0106fa9..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,31 +28,30 @@ 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 } + 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 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: trimmedQueueIds + ) 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 +70,7 @@ extension Glia { ) startRootCoordinator( - with: interactor, + with: createdInteractor, viewFactory: viewFactory, sceneProvider: sceneProvider, engagementKind: engagementKind, @@ -80,7 +79,7 @@ extension Glia { } func companyName( - using interactor: Interactor, + using configuration: Configuration, themeCompanyName: String? ) -> String { let companyNameStringKey = "general.company_name" @@ -98,8 +97,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 { @@ -141,7 +140,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, @@ -165,7 +163,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.Deprecated.swift b/GliaWidgets/Public/Glia/Glia.Deprecated.swift index 96e8c7dd4..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, @@ -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, @@ -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. @@ -149,11 +127,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. """ ) @@ -168,22 +146,65 @@ 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 { 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 + ) + } + + /// 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 344555c0e..d4d42fb32 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. @@ -17,11 +18,12 @@ 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( engagement: EngagementKind, + in queueIds: [String], uiConfig: RemoteConfiguration, assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard, features: Features = .all, @@ -38,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 @@ -46,6 +48,7 @@ extension Glia { try startEngagement( engagementKind: engagement, + in: queueIds, theme: theme, features: features, sceneProvider: sceneProvider diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index 538370f33..a34c951b8 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? @@ -94,6 +95,8 @@ public class Glia { var uiConfig: RemoteConfiguration? var assetsBuilder: RemoteConfiguration.AssetsBuilder = .standard + private(set) var configuration: Configuration? + init(environment: Environment) { self.environment = environment } @@ -101,46 +104,70 @@ 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 + // `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 { + case .visitorCodeIsRequested: + self.setupInteractor(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.stringProviding = .init(getRemoteString: self.environment.coreSdk.localeProvider.getRemoteString) + 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) + + if let engagement = self.environment.coreSdk.getCurrentEngagement(), + engagement.source == .callVisualizer { + self.setupInteractor(configuration: configuration) + } - createdInteractor.state = self.environment.coreSdk - .getCurrentEngagement()?.engagedOperator - .map(InteractorState.engaged) ?? createdInteractor.state + completion(.success(())) + 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 + } + } - callback() + debugPrint("💥 Core SDK configuration is not valid. Unexpected error='\(error)'.") + completion(.failure(errorForCompletion)) } } - - startObservingInteractorEvents() } /// Minimizes engagement view if ongoing engagement exists. @@ -202,11 +229,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 } @@ -236,14 +263,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 } @@ -257,7 +284,7 @@ public class Glia { rootCoordinator = nil } - guard interactor != nil else { + guard configuration != nil else { completion(.failure(GliaError.sdkIsNotConfigured)) return } @@ -272,10 +299,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 } @@ -296,7 +323,7 @@ public class Glia { } } -// MARK: - Private +// MARK: - Internal extension Glia { internal func startObservingInteractorEvents() { @@ -332,7 +359,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 { @@ -344,4 +371,35 @@ 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 { + /// Used for unit tests only + var isConfigured: Bool { + configuration != nil + } } +#endif 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/Resources/Assets.xcassets/AppIcon.appiconset/512x512_light.png b/GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/512x512_light.png deleted file mode 100644 index 691326203..000000000 Binary files a/GliaWidgets/Resources/Assets.xcassets/AppIcon.appiconset/512x512_light.png and /dev/null differ 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 - } -} 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+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+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 ad8760831..bd9893135 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() { @@ -58,7 +59,8 @@ public final class CallVisualizer { self?.environment.eventHandler?(.maximized) } }, - orientationManager: environment.orientationManager + orientationManager: environment.orientationManager, + proximityManager: environment.proximityManager ) ) }() @@ -80,6 +82,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 +106,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/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..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( @@ -220,7 +219,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 5376ac2f8..80a782456 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 ) @@ -254,7 +255,8 @@ extension EngagementCoordinator { interactor: interactor, startSocketObservation: environment.startSocketObservation, stopSocketObservation: environment.stopSocketObservation, - createSendMessagePayload: environment.createSendMessagePayload + createSendMessagePayload: environment.createSendMessagePayload, + proximityManager: environment.proximityManager ), startWithSecureTranscriptFlow: false ) @@ -342,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 @@ -452,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, @@ -478,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/CoreSDKClient/CoreSDKClient.Interface.swift b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift index cb14acb1c..19c5a279b 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 @@ -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 @@ -249,4 +250,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 new file mode 100644 index 000000000..6613b4eb2 --- /dev/null +++ b/GliaWidgets/Sources/CoreSDKConfigurator/CoreSDKConfigurator.Interface.swift @@ -0,0 +1,24 @@ +import Foundation + +struct CoreSDKConfigurator { + var configureWithInteractor: CoreSdkClient.ConfigureWithInteractor + var configureWithConfiguration: (Configuration, @escaping (Result) -> 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, + manualLocaleOverride: configuration.manualLocaleOverride + ) + 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..acf794415 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift @@ -45,6 +45,8 @@ extension Glia { var screenShareHandler: ScreenShareHandler 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 5d96bd07c..e86916ed4 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift @@ -32,6 +32,11 @@ extension Glia.Environment { uiApplication: .live, uiDevice: .live, notificationCenter: .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 c4d11734d..3f282149d 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift @@ -26,7 +26,9 @@ extension Glia.Environment { createFileUploadListModel: SecureConversations.FileUploadListViewModel.mock(environment:), screenShareHandler: .mock, messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, - orientationManager: .mock() + orientationManager: .mock(), + coreSDKConfigurator: .mock, + proximityManager: .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..207d864a0 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,14 +55,10 @@ 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 var state: InteractorState = .none { didSet { @@ -74,12 +70,12 @@ 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 } @@ -104,34 +100,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 +112,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,17 +151,13 @@ 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( success: @escaping () -> Void, failure: @escaping (CoreSdkClient.SalemoveError) -> Void ) { - isEngagementEndedByVisitor = true - switch state { case .none: success() @@ -240,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() } } @@ -276,7 +237,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(_:)) @@ -372,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/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/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 f0d35516e..c4f6cdf3f 100644 --- a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift @@ -10,20 +10,15 @@ extension CallViewController { viewModel: viewModel, viewFactory: viewFactory, environment: .init( - uiApplication: .mock, - uiScreen: .mock, - uiDevice: .mock, notificationCenter: .mock ) ) } 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 +50,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 +83,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?() + callback(.success(())) } let interactor = Interactor.mock( - configuration: conf, queueId: queueId, environment: interactorEnv ) @@ -146,11 +137,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 +183,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 +217,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/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..d6ef76044 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] = [ @@ -200,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 @@ -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()) @@ -545,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/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/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/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/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..1b7fa9187 100644 --- a/GliaWidgetsTests/Glia.Environment.Failing.swift +++ b/GliaWidgetsTests/Glia.Environment.Failing.swift @@ -54,8 +54,10 @@ extension Glia.Environment { return .mock() }, screenShareHandler: .mock, - messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, - orientationManager: .mock() + messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, + orientationManager: .mock(), + coreSDKConfigurator: .failing, + proximityManager: .failing ) } 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/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/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+CustomCard.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+CustomCard.swift index c33029aea..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 805b1dbdc..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 008911f97..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/ChatViewModel/ChatViewModelTests.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift index 64593b0a2..6b289fa2a 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 ) ) @@ -112,32 +113,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() viewModelEnv.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] } @@ -155,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() } @@ -165,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 @@ -177,7 +152,7 @@ class ChatViewModelTests: XCTestCase { XCTAssertEqual(lastItemKind, .transferring) } - + func test_onEngagementTransferRemovesTransferringItemFromChat() throws { var interactorEnv: Interactor.Environment = .failing interactorEnv.gcd.mainQueue.asyncIfNeeded = { $0() } @@ -190,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 @@ -204,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() } @@ -284,7 +259,7 @@ class ChatViewModelTests: XCTestCase { // Then XCTAssertEqual(calls, [.fetchSiteConfigurations]) } - + func test_handleUrlWithPhoneOpensURLWithUIApplication() throws { enum Call: Equatable { case openUrl(URL) } @@ -304,7 +279,7 @@ class ChatViewModelTests: XCTestCase { XCTAssertEqual(calls, [.openUrl(telUrl)]) } - + func test_handleUrlWithEmailOpensURLWithUIApplication() throws { enum Call: Equatable { case openUrl(URL) } @@ -319,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] = [] @@ -344,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)]) @@ -352,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] } @@ -366,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) @@ -392,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( @@ -429,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 @@ -620,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))) @@ -670,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/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+StartEngagement.swift b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift index df6064d91..babd2907d 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(.success(())) + } + 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(.success(())) + } + 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(.success(())) + } + 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(.success(())) + } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } + environment.coreSdk.localeProvider.getRemoteString = { _ in nil } let sdk = Glia(environment: environment) @@ -80,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") @@ -107,10 +142,10 @@ extension GliaTests { } environment.coreSdk.localeProvider.getRemoteString = { _ in "Glia" } - environment.coreSdk.configureWithInteractor = { _ in } - environment.coreSdk.configureWithConfiguration = { _, completion in - completion?() + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion(.success(())) } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.getCurrentEngagement = { nil } let sdk = Glia(environment: environment) @@ -120,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") @@ -145,11 +185,21 @@ extension GliaTests { environment: .failing ) } + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + 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") @@ -173,11 +223,21 @@ extension GliaTests { environment: .failing ) } + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + 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") @@ -203,10 +263,10 @@ extension GliaTests { } environment.coreSdk.localeProvider.getRemoteString = { _ in "" } - environment.coreSdk.configureWithInteractor = { _ in } - environment.coreSdk.configureWithConfiguration = { _, completion in - completion?() + environment.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion(.success(())) } + environment.coreSDKConfigurator.configureWithInteractor = { _ in } environment.coreSdk.getCurrentEngagement = { nil } let sdk = Glia(environment: environment) @@ -215,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") @@ -242,20 +307,20 @@ 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 ) - 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 46dcde9ca..25b7a9ebe 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 } @@ -51,10 +52,13 @@ 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(.success(())) + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } gliaEnv.uuid = { .mock } gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } @@ -91,16 +95,12 @@ final class GliaTests: XCTestCase { } let sdk = Glia(environment: gliaEnv) - let kind = EngagementKind.audioCall - let site = "site" - 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 @@ -128,16 +128,17 @@ final class GliaTests: XCTestCase { var gliaEnv = Glia.Environment.failing gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion(.success(())) + } + 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 +156,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(.success(())) + } + 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 +183,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(.success(())) + } + 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() @@ -204,19 +209,26 @@ 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 }) gliaEnv.gcd.mainQueue.asyncIfNeeded = { callback in callback() } gliaEnv.notificationCenter.addObserverClosure = { _, _, _, _ in } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion(.success(())) + } + 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() @@ -230,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) } } @@ -283,4 +295,110 @@ 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(.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()) {} + XCTAssertTrue(sdk.isConfigured) + + try? sdk.configure(with: .mock()) {} + XCTAssertFalse(sdk.isConfigured) + } + + 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()) {} + XCTAssertFalse(sdk.isConfigured) + } + + func test_engagementCoordinatorGetsDeallocated() throws { + var environment = Glia.Environment.failing + environment.coreSDKConfigurator.configureWithConfiguration = { _, callback in + callback(.success(())) + } + 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) + } + + 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(.success(())) + } + 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 887f719f3..44da62bab 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) ) @@ -158,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) @@ -190,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)) } @@ -225,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())) } @@ -424,7 +365,7 @@ class InteractorTests: XCTestCase { } } - interactor.end(with: .natural) + interactor.end(with: .operatorHungUp) XCTAssertEqual(callbacks, [.ended]) } @@ -439,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 @@ -506,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) + } } 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/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/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) 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/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 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 31192450a..09f0b36d5 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) } } @@ -92,18 +90,25 @@ class ViewController: UIViewController { } private func showErrorAlert(using error: Error) { - guard let gliaError = error as? GliaError else { return } - - switch error { - 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") - 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)'.") + 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") + } + } else { + alert(message: "Failed to execute with error: \(error)\nCheck Glia parameters in Settings") } } @@ -120,20 +125,26 @@ 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) + } } } @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) } } } @@ -196,13 +207,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) } } @@ -234,12 +243,19 @@ extension ViewController { self?.configureButton.accessibilityIdentifier = originalIdentifier debugPrint(printable) } + 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 configuring the SDK") + completion?(.failure(error)) + } } } catch { completionBlock(error) @@ -288,17 +304,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, - 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] { @@ -348,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 @@ -378,22 +406,26 @@ 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] 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/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: 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 %} 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