diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..bcafef811 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +**Jira issue:** +https://glia.atlassian.net/browse/MOB-xxx + +**What was solved?** + +**Release notes:** + + - [ ] Feature + - [ ] Ignore + - [ ] Release notes (Is it clear from the description here?) + - [ ] Migration guide (If changes are needed for integrator already using the SDK - what needs to be communicated? Add underneath please) + +**Additional info:** + + - [ ] Tests fixed, added? Unit, acceptance, snapshots? + - [ ] Logging necessary for future troubleshooting of customer issues added? + +**Screenshots:** diff --git a/GliaWidgets.podspec b/GliaWidgets.podspec index 02e348371..c0a37caed 100644 --- a/GliaWidgets.podspec +++ b/GliaWidgets.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GliaWidgets' - s.version = '2.1.0' + s.version = '2.2.0' 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.2' + s.dependency 'GliaCoreSDK', '1.1.5' end diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index 2b4208b5c..e8ee8724f 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -183,9 +183,12 @@ 311CAFCD29F8FAE20067B59F /* SecureConversations.TranscriptModel+CustomCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311CAFCC29F8FAE20067B59F /* SecureConversations.TranscriptModel+CustomCard.swift */; }; 313EBD552943116E008E9597 /* SecureConversations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBD542943116E008E9597 /* SecureConversations.swift */; }; 3142696A29FFB712003DF62E /* Interactor.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3142696929FFB712003DF62E /* Interactor.Failing.swift */; }; + 3146C9432AB1851C0047D8CC /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3146C9422AB1851C0047D8CC /* LocalizationTests.swift */; }; + 3146C9492AB18AC70047D8CC /* Localization+StringProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3146C9482AB18AC70047D8CC /* Localization+StringProviding.swift */; }; 315BAB1A29ADFEBC00FF284B /* ConfirmationStyle+TitleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BAB1929ADFEBC00FF284B /* ConfirmationStyle+TitleStyle.swift */; }; 315BAB1C29ADFEC800FF284B /* ConfirmationStyle+SubtitleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BAB1B29ADFEC800FF284B /* ConfirmationStyle+SubtitleStyle.swift */; }; 315BAB1E29ADFED800FF284B /* ConfirmationStyle+CheckMessagesButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BAB1D29ADFED800FF284B /* ConfirmationStyle+CheckMessagesButtonStyle.swift */; }; + 31882C9B2AA21B71009DE4BD /* Localization+Templates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31882C9A2AA21B71009DE4BD /* Localization+Templates.swift */; }; 3189DD9429DEFAC600D68E9F /* SecureConversations.WelcomeViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3189DD9329DEFAC600D68E9F /* SecureConversations.WelcomeViewModelSpec.swift */; }; 3189DD9629E4331200D68E9F /* SecureConversations.WelcomeViewModel.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3189DD9529E4331200D68E9F /* SecureConversations.WelcomeViewModel.Mock.swift */; }; 3197F7AD29E6A5C8008EE9F7 /* SecureConversations.FileUploadListView.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197F7AC29E6A5C8008EE9F7 /* SecureConversations.FileUploadListView.Mock.swift */; }; @@ -194,6 +197,7 @@ 3197F7B429F7C26A008EE9F7 /* SecureConversations.ChatWithTranscriptViewModel+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197F7B329F7C26A008EE9F7 /* SecureConversations.ChatWithTranscriptViewModel+Hashable.swift */; }; 3197F7B629F7C2E5008EE9F7 /* SecureConversations.SecureChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197F7B529F7C2E5008EE9F7 /* SecureConversations.SecureChatModel.swift */; }; 3197F7B829F7C318008EE9F7 /* SecureConversations.CommonEngagementModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197F7B729F7C318008EE9F7 /* SecureConversations.CommonEngagementModel.swift */; }; + 31B1F8A92AB093ED009EC5AD /* StringProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B1F8A82AB093ED009EC5AD /* StringProviding.swift */; }; 31D286AD2A00DD2C009192A6 /* SecureConversations.ConfirmationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D286AC2A00DD2C009192A6 /* SecureConversations.ConfirmationViewModelTests.swift */; }; 31D286AF2A00DE2B009192A6 /* SecureConversations.ConfirmationViewModel.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D286AE2A00DE2B009192A6 /* SecureConversations.ConfirmationViewModel.Mock.swift */; }; 31DB0C01287C2EFC00FB288E /* StaticValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DB0C00287C2EFC00FB288E /* StaticValues.swift */; }; @@ -296,8 +300,8 @@ 75C48498299AC71F003EC223 /* Header+Playbook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C48497299AC71F003EC223 /* Header+Playbook.swift */; }; 75CF8D6129B3F1E400CB1524 /* Configuration+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75CF8D6029B3F1E400CB1524 /* Configuration+Mock.swift */; }; 75CF8D6329B7DD8300CB1524 /* SecureConversations+ConfirmationStyle+RemoteConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75CF8D6229B7DD8300CB1524 /* SecureConversations+ConfirmationStyle+RemoteConfig.swift */; }; - 75CF8D9129C3A85C00CB1524 /* SecureConversationsWelcomeScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75CF8D9029C3A85C00CB1524 /* SecureConversationsWelcomeScreenTests.swift */; }; - 75CF8DAD29C8F2B500CB1524 /* SecureConversationsConfirmationScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75CF8DAC29C8F2B500CB1524 /* SecureConversationsConfirmationScreenTests.swift */; }; + 75CF8D9129C3A85C00CB1524 /* SecureConversationsWelcomeScreenVoiceOverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75CF8D9029C3A85C00CB1524 /* SecureConversationsWelcomeScreenVoiceOverTests.swift */; }; + 75CF8DAD29C8F2B500CB1524 /* SecureConversationsConfirmationScreenVoiceOverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75CF8DAC29C8F2B500CB1524 /* SecureConversationsConfirmationScreenVoiceOverTests.swift */; }; 75F58EE127E7D5300065BA2D /* Survey.ViewController.Props.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F58EE027E7D5300065BA2D /* Survey.ViewController.Props.swift */; }; 75FF151427F3A2D600FE7BE2 /* Theme+Survey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75FF151327F3A2D600FE7BE2 /* Theme+Survey.swift */; }; 75FF151727F4E13900FE7BE2 /* Theme.Survey.BooleanQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75FF151627F4E13900FE7BE2 /* Theme.Survey.BooleanQuestion.swift */; }; @@ -307,16 +311,12 @@ 84265E07298AE96B00D65842 /* ScreenSharingViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E06298AE96B00D65842 /* ScreenSharingViewModelTests.swift */; }; 84265E4B298D7B1900D65842 /* CallVisualizer.Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E49298D7B1900D65842 /* CallVisualizer.Coordinator.swift */; }; 84265E4C298D7B1900D65842 /* CallVisualizer.Coordinator.Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E4A298D7B1900D65842 /* CallVisualizer.Coordinator.Environment.swift */; }; - 84265E5D298D7B2900D65842 /* ScreenSharingViewModel+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E4F298D7B2900D65842 /* ScreenSharingViewModel+Environment.swift */; }; - 84265E5E298D7B2900D65842 /* ScreenSharingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E50298D7B2900D65842 /* ScreenSharingViewModel.swift */; }; - 84265E5F298D7B2900D65842 /* ScreenSharingViewModel+Output.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E51298D7B2900D65842 /* ScreenSharingViewModel+Output.swift */; }; 84265E60298D7B2900D65842 /* ScreenSharingCoordinator+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E53298D7B2900D65842 /* ScreenSharingCoordinator+Environment.swift */; }; 84265E61298D7B2900D65842 /* ScreenSharingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E54298D7B2900D65842 /* ScreenSharingCoordinator.swift */; }; 84265E62298D7B2900D65842 /* ScreenSharingCoordinator+DelegateEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E55298D7B2900D65842 /* ScreenSharingCoordinator+DelegateEvent.swift */; }; 84265E63298D7B2900D65842 /* ScreenSharingViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E56298D7B2900D65842 /* ScreenSharingViewStyle.swift */; }; 84265E64298D7B2900D65842 /* ScreenSharingViewStyle.Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E57298D7B2900D65842 /* ScreenSharingViewStyle.Accessibility.swift */; }; 84265E65298D7B2900D65842 /* ScreenSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E59298D7B2900D65842 /* ScreenSharingView.swift */; }; - 84265E66298D7B2900D65842 /* ScreenSharingViewController+Props.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E5B298D7B2900D65842 /* ScreenSharingViewController+Props.swift */; }; 84265E67298D7B2900D65842 /* ScreenSharingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E5C298D7B2900D65842 /* ScreenSharingViewController.swift */; }; 84265E6B29912E2100D65842 /* RemoteConfiguration+CallVisualizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E6A29912E2100D65842 /* RemoteConfiguration+CallVisualizer.swift */; }; 84265E6E29914DDA00D65842 /* ViewController+CallVisualizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E6D29914DDA00D65842 /* ViewController+CallVisualizer.swift */; }; @@ -368,7 +368,7 @@ 846A5C3E29D1C7B00049B29F /* CallVisualizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846A5C3D29D1C7B00049B29F /* CallVisualizerTests.swift */; }; 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 /* AlertViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E822728996A5C008EFBF0 /* AlertViewControllerTests.swift */; }; + 846E822828996A5C008EFBF0 /* AlertViewControllerVoiceOverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E822728996A5C008EFBF0 /* AlertViewControllerVoiceOverTests.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 */; }; @@ -399,6 +399,7 @@ 8491AF602AA1EBB600CC3E72 /* TranscriptModelTests+URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491AF5F2AA1EBB600CC3E72 /* TranscriptModelTests+URLs.swift */; }; 8491AF632AA20F9D00CC3E72 /* GliaViewControllerDelegateMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491AF622AA20F9D00CC3E72 /* GliaViewControllerDelegateMock.swift */; }; 8491AF652AAB281500CC3E72 /* ChatViewModelTests+CustomCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491AF642AAB281500CC3E72 /* ChatViewModelTests+CustomCard.swift */; }; + 8491AF672AB8707600CC3E72 /* ChatViewModelTests+Transferring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491AF662AB8707600CC3E72 /* ChatViewModelTests+Transferring.swift */; }; 84A318A12869ECFC00CA1DE5 /* Unavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A318A02869ECFC00CA1DE5 /* Unavailable.swift */; }; 84CFB7732822700000167258 /* Theme.Button.Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CFB7722822700000167258 /* Theme.Button.Accessibility.swift */; }; 84D2292B28C61C8D00F64FE7 /* WKNavigationPolicyProvider.CustomResponseCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2292A28C61C8D00F64FE7 /* WKNavigationPolicyProvider.CustomResponseCard.swift */; }; @@ -518,6 +519,7 @@ AF10ED8F29BF849A00E85309 /* UnreadMessageDividerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF10ED8E29BF849A00E85309 /* UnreadMessageDividerView.swift */; }; AF10ED9129BF85C700E85309 /* UnreadMessageDividerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF10ED9029BF85C700E85309 /* UnreadMessageDividerStyle.swift */; }; AF11F30728BE6F0C002ACEB4 /* UIAlertController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11F30628BE6F0C002ACEB4 /* UIAlertController+Extensions.swift */; }; + AF18F1D52ABD954500121627 /* View+Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF18F1D42ABD954500121627 /* View+Accessibility.swift */; }; AF22C8852A6154780004BF3C /* AlertViewControllerLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF22C8842A6154780004BF3C /* AlertViewControllerLayoutTests.swift */; }; AF22C8872A6182AF0004BF3C /* BubbleViewLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF22C8862A6182AF0004BF3C /* BubbleViewLayoutTests.swift */; }; AF22C8892A6184C50004BF3C /* CallViewControllerLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF22C8882A6184C50004BF3C /* CallViewControllerLayoutTests.swift */; }; @@ -589,18 +591,24 @@ C03A8049292BC8DB00DDECA6 /* CallViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03A8048292BC8DB00DDECA6 /* CallViewControllerTests.swift */; }; C05AB01C295F416700AA381F /* VisitorCodeCloseButtonProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05AB01B295F416700AA381F /* VisitorCodeCloseButtonProperties.swift */; }; C05E3EDE29C99E070013BC81 /* ProximityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05E3EDD29C99E070013BC81 /* ProximityManager.swift */; }; + C06152D52AB1BC1300063BF8 /* Font+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06152D42AB1BC1300063BF8 /* Font+Extensions.swift */; }; + C06152DA2AB1BC4300063BF8 /* OrientationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06152D92AB1BC4300063BF8 /* OrientationManager.swift */; }; C06A757F296EC76B006B69A2 /* VisitorCodeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06A757E296EC76B006B69A2 /* VisitorCodeStyle.swift */; }; C06A7582296EC856006B69A2 /* NumberSlotStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06A7581296EC856006B69A2 /* NumberSlotStyle.swift */; }; C06A7584296EC9DC006B69A2 /* NumberSlotStyle.Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06A7583296EC9DC006B69A2 /* NumberSlotStyle.Accessibility.swift */; }; C06A7586296ECC57006B69A2 /* VisitorCodeStyle.Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06A7585296ECC57006B69A2 /* VisitorCodeStyle.Accessibility.swift */; }; C06A7588296ECD75006B69A2 /* Theme+VisitorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06A7587296ECD75006B69A2 /* Theme+VisitorCode.swift */; }; + C07F62462ABC322B003EFC97 /* OrientationManager.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07F62452ABC322B003EFC97 /* OrientationManager.Mock.swift */; }; + C07F62772AC1BA2B003EFC97 /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07F62762AC1BA2B003EFC97 /* UIViewController+Extensions.swift */; }; + C07F62792AC2D2E8003EFC97 /* BackgroundSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07F62782AC2D2E8003EFC97 /* BackgroundSwiftUI.swift */; }; + C07F627D2AC2F31F003EFC97 /* ScreenSharingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07F627C2AC2F31F003EFC97 /* ScreenSharingViewModel.swift */; }; + C07F62812AC3057C003EFC97 /* ScreenSharingViewModel.mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07F62802AC3057C003EFC97 /* ScreenSharingViewModel.mock.swift */; }; + C07F62832AC33BB9003EFC97 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07F62822AC33BB9003EFC97 /* UIView+Extensions.swift */; }; C07FA04029AF542A00E9FB7F /* ScreenSharingViewStyle+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84265E0B298AECBA00D65842 /* ScreenSharingViewStyle+Mock.swift */; }; - C07FA04329AF551D00E9FB7F /* ScreenSharingView.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04129AF550500E9FB7F /* ScreenSharingView.Mock.swift */; }; - C07FA04629AF560A00E9FB7F /* ScreenSharingViewController.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04429AF55F600E9FB7F /* ScreenSharingViewController.Mock.swift */; }; C07FA04B29AF83B900E9FB7F /* ActionButton.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04929AF83A400E9FB7F /* ActionButton.Mock.swift */; }; - C07FA04F29B0E41A00E9FB7F /* ScreenShareViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04C29B0E41A00E9FB7F /* ScreenShareViewControllerTests.swift */; }; - C07FA05029B0E41A00E9FB7F /* VideoCallViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04D29B0E41A00E9FB7F /* VideoCallViewControllerTests.swift */; }; - C07FA05129B0E41A00E9FB7F /* VisitorCodeViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04E29B0E41A00E9FB7F /* VisitorCodeViewControllerTests.swift */; }; + C07FA04F29B0E41A00E9FB7F /* ScreenShareViewControllerVoiceOverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04C29B0E41A00E9FB7F /* ScreenShareViewControllerVoiceOverTests.swift */; }; + C07FA05029B0E41A00E9FB7F /* VideoCallViewControllerVoiceOverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04D29B0E41A00E9FB7F /* VideoCallViewControllerVoiceOverTests.swift */; }; + C07FA05129B0E41A00E9FB7F /* VisitorCodeViewControllerVoiceOverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07FA04E29B0E41A00E9FB7F /* VisitorCodeViewControllerVoiceOverTests.swift */; }; C0857DE528D470C1008D171D /* Theme+Shadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0857DE428D470C1008D171D /* Theme+Shadow.swift */; }; C0857DE728D470F2008D171D /* Theme+Layer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0857DE628D470F2008D171D /* Theme+Layer.swift */; }; C0857DEB28D482D0008D171D /* Theme+Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0857DEA28D482D0008D171D /* Theme+Button.swift */; }; @@ -640,6 +648,9 @@ C0D2F08B29A4E95700803B47 /* ConnectView.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D2F08929A4E92D00803B47 /* ConnectView.Mock.swift */; }; C0D2F08C29A4EBA900803B47 /* VIdeoCallView.Environment.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D2F06F29A4DB5C00803B47 /* VIdeoCallView.Environment.Mock.swift */; }; C0D2F08F29A61A8D00803B47 /* VideoCallViewController.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D2F08D29A61A7800803B47 /* VideoCallViewController.Mock.swift */; }; + C0E948042AB1D5D200890026 /* ActionButtonSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E948032AB1D5D200890026 /* ActionButtonSwiftUI.swift */; }; + C0E948062AB1D64700890026 /* HeaderButtonSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E948052AB1D64700890026 /* HeaderButtonSwiftUI.swift */; }; + C0E948092AB1D6AB00890026 /* HeaderSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E948082AB1D6AB00890026 /* HeaderSwiftUI.swift */; }; C2B201AEDBE3A53369DF524F /* Pods_GliaWidgetsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CCF7E6C5499635E67EF6A604 /* Pods_GliaWidgetsTests.framework */; }; C4119E06268F41D1004DFEFB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C4119E05268F41D1004DFEFB /* Main.storyboard */; }; C42463742673ABE10082C135 /* ScreenShareHandler.Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42463732673ABE10082C135 /* ScreenShareHandler.Interface.swift */; }; @@ -913,9 +924,12 @@ 311CAFCC29F8FAE20067B59F /* SecureConversations.TranscriptModel+CustomCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecureConversations.TranscriptModel+CustomCard.swift"; sourceTree = ""; }; 313EBD542943116E008E9597 /* SecureConversations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.swift; sourceTree = ""; }; 3142696929FFB712003DF62E /* Interactor.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interactor.Failing.swift; sourceTree = ""; }; + 3146C9422AB1851C0047D8CC /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = ""; }; + 3146C9482AB18AC70047D8CC /* Localization+StringProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Localization+StringProviding.swift"; sourceTree = ""; }; 315BAB1929ADFEBC00FF284B /* ConfirmationStyle+TitleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfirmationStyle+TitleStyle.swift"; sourceTree = ""; }; 315BAB1B29ADFEC800FF284B /* ConfirmationStyle+SubtitleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfirmationStyle+SubtitleStyle.swift"; sourceTree = ""; }; 315BAB1D29ADFED800FF284B /* ConfirmationStyle+CheckMessagesButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfirmationStyle+CheckMessagesButtonStyle.swift"; sourceTree = ""; }; + 31882C9A2AA21B71009DE4BD /* Localization+Templates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Localization+Templates.swift"; sourceTree = ""; }; 3189DD9329DEFAC600D68E9F /* SecureConversations.WelcomeViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.WelcomeViewModelSpec.swift; sourceTree = ""; }; 3189DD9529E4331200D68E9F /* SecureConversations.WelcomeViewModel.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.WelcomeViewModel.Mock.swift; sourceTree = ""; }; 3197F7AC29E6A5C8008EE9F7 /* SecureConversations.FileUploadListView.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.FileUploadListView.Mock.swift; sourceTree = ""; }; @@ -924,6 +938,7 @@ 3197F7B329F7C26A008EE9F7 /* SecureConversations.ChatWithTranscriptViewModel+Hashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecureConversations.ChatWithTranscriptViewModel+Hashable.swift"; sourceTree = ""; }; 3197F7B529F7C2E5008EE9F7 /* SecureConversations.SecureChatModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.SecureChatModel.swift; sourceTree = ""; }; 3197F7B729F7C318008EE9F7 /* SecureConversations.CommonEngagementModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.CommonEngagementModel.swift; sourceTree = ""; }; + 31B1F8A82AB093ED009EC5AD /* StringProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringProviding.swift; sourceTree = ""; }; 31D286AC2A00DD2C009192A6 /* SecureConversations.ConfirmationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.ConfirmationViewModelTests.swift; sourceTree = ""; }; 31D286AE2A00DE2B009192A6 /* SecureConversations.ConfirmationViewModel.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversations.ConfirmationViewModel.Mock.swift; sourceTree = ""; }; 31DB0C00287C2EFC00FB288E /* StaticValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticValues.swift; sourceTree = ""; }; @@ -1020,8 +1035,8 @@ 75C48497299AC71F003EC223 /* Header+Playbook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Header+Playbook.swift"; sourceTree = ""; }; 75CF8D6029B3F1E400CB1524 /* Configuration+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Configuration+Mock.swift"; sourceTree = ""; }; 75CF8D6229B7DD8300CB1524 /* SecureConversations+ConfirmationStyle+RemoteConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecureConversations+ConfirmationStyle+RemoteConfig.swift"; sourceTree = ""; }; - 75CF8D9029C3A85C00CB1524 /* SecureConversationsWelcomeScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversationsWelcomeScreenTests.swift; sourceTree = ""; }; - 75CF8DAC29C8F2B500CB1524 /* SecureConversationsConfirmationScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversationsConfirmationScreenTests.swift; sourceTree = ""; }; + 75CF8D9029C3A85C00CB1524 /* SecureConversationsWelcomeScreenVoiceOverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversationsWelcomeScreenVoiceOverTests.swift; sourceTree = ""; }; + 75CF8DAC29C8F2B500CB1524 /* SecureConversationsConfirmationScreenVoiceOverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureConversationsConfirmationScreenVoiceOverTests.swift; sourceTree = ""; }; 75F58EE027E7D5300065BA2D /* Survey.ViewController.Props.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Survey.ViewController.Props.swift; sourceTree = ""; }; 75FF151327F3A2D600FE7BE2 /* Theme+Survey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Survey.swift"; sourceTree = ""; }; 75FF151627F4E13900FE7BE2 /* Theme.Survey.BooleanQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.Survey.BooleanQuestion.swift; sourceTree = ""; }; @@ -1033,16 +1048,12 @@ 84265E0B298AECBA00D65842 /* ScreenSharingViewStyle+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScreenSharingViewStyle+Mock.swift"; sourceTree = ""; }; 84265E49298D7B1900D65842 /* CallVisualizer.Coordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallVisualizer.Coordinator.swift; sourceTree = ""; }; 84265E4A298D7B1900D65842 /* CallVisualizer.Coordinator.Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallVisualizer.Coordinator.Environment.swift; sourceTree = ""; }; - 84265E4F298D7B2900D65842 /* ScreenSharingViewModel+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ScreenSharingViewModel+Environment.swift"; sourceTree = ""; }; - 84265E50298D7B2900D65842 /* ScreenSharingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewModel.swift; sourceTree = ""; }; - 84265E51298D7B2900D65842 /* ScreenSharingViewModel+Output.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ScreenSharingViewModel+Output.swift"; sourceTree = ""; }; 84265E53298D7B2900D65842 /* ScreenSharingCoordinator+Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ScreenSharingCoordinator+Environment.swift"; sourceTree = ""; }; 84265E54298D7B2900D65842 /* ScreenSharingCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenSharingCoordinator.swift; sourceTree = ""; }; 84265E55298D7B2900D65842 /* ScreenSharingCoordinator+DelegateEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ScreenSharingCoordinator+DelegateEvent.swift"; sourceTree = ""; }; 84265E56298D7B2900D65842 /* ScreenSharingViewStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewStyle.swift; sourceTree = ""; }; 84265E57298D7B2900D65842 /* ScreenSharingViewStyle.Accessibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewStyle.Accessibility.swift; sourceTree = ""; }; 84265E59298D7B2900D65842 /* ScreenSharingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenSharingView.swift; sourceTree = ""; }; - 84265E5B298D7B2900D65842 /* ScreenSharingViewController+Props.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ScreenSharingViewController+Props.swift"; sourceTree = ""; }; 84265E5C298D7B2900D65842 /* ScreenSharingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewController.swift; sourceTree = ""; }; 84265E6A29912E2100D65842 /* RemoteConfiguration+CallVisualizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteConfiguration+CallVisualizer.swift"; sourceTree = ""; }; 84265E6D29914DDA00D65842 /* ViewController+CallVisualizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController+CallVisualizer.swift"; sourceTree = ""; }; @@ -1094,7 +1105,7 @@ 846A5C3D29D1C7B00049B29F /* CallVisualizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVisualizerTests.swift; sourceTree = ""; }; 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 /* AlertViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertViewControllerTests.swift; sourceTree = ""; }; + 846E822728996A5C008EFBF0 /* AlertViewControllerVoiceOverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertViewControllerVoiceOverTests.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 = ""; }; @@ -1126,6 +1137,7 @@ 8491AF5F2AA1EBB600CC3E72 /* TranscriptModelTests+URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TranscriptModelTests+URLs.swift"; sourceTree = ""; }; 8491AF622AA20F9D00CC3E72 /* GliaViewControllerDelegateMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GliaViewControllerDelegateMock.swift; sourceTree = ""; }; 8491AF642AAB281500CC3E72 /* ChatViewModelTests+CustomCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatViewModelTests+CustomCard.swift"; sourceTree = ""; }; + 8491AF662AB8707600CC3E72 /* ChatViewModelTests+Transferring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatViewModelTests+Transferring.swift"; sourceTree = ""; }; 84A318A02869ECFC00CA1DE5 /* Unavailable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Unavailable.swift; sourceTree = ""; }; 84CFB7722822700000167258 /* Theme.Button.Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.Button.Accessibility.swift; sourceTree = ""; }; 84D2292A28C61C8D00F64FE7 /* WKNavigationPolicyProvider.CustomResponseCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationPolicyProvider.CustomResponseCard.swift; sourceTree = ""; }; @@ -1248,6 +1260,7 @@ AF10ED8E29BF849A00E85309 /* UnreadMessageDividerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadMessageDividerView.swift; sourceTree = ""; }; AF10ED9029BF85C700E85309 /* UnreadMessageDividerStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadMessageDividerStyle.swift; sourceTree = ""; }; AF11F30628BE6F0C002ACEB4 /* UIAlertController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Extensions.swift"; sourceTree = ""; }; + AF18F1D42ABD954500121627 /* View+Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Accessibility.swift"; sourceTree = ""; }; AF22C8842A6154780004BF3C /* AlertViewControllerLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewControllerLayoutTests.swift; sourceTree = ""; }; AF22C8862A6182AF0004BF3C /* BubbleViewLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleViewLayoutTests.swift; sourceTree = ""; }; AF22C8882A6184C50004BF3C /* CallViewControllerLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewControllerLayoutTests.swift; sourceTree = ""; }; @@ -1320,17 +1333,23 @@ C05AB016295DA9FC00AA381F /* AlertViewController+VisitorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlertViewController+VisitorCode.swift"; sourceTree = ""; }; C05AB01B295F416700AA381F /* VisitorCodeCloseButtonProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitorCodeCloseButtonProperties.swift; sourceTree = ""; }; C05E3EDD29C99E070013BC81 /* ProximityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityManager.swift; sourceTree = ""; }; + C06152D42AB1BC1300063BF8 /* Font+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Extensions.swift"; sourceTree = ""; }; + C06152D92AB1BC4300063BF8 /* OrientationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationManager.swift; sourceTree = ""; }; C06A757E296EC76B006B69A2 /* VisitorCodeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitorCodeStyle.swift; sourceTree = ""; }; C06A7581296EC856006B69A2 /* NumberSlotStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberSlotStyle.swift; sourceTree = ""; }; C06A7583296EC9DC006B69A2 /* NumberSlotStyle.Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberSlotStyle.Accessibility.swift; sourceTree = ""; }; C06A7585296ECC57006B69A2 /* VisitorCodeStyle.Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitorCodeStyle.Accessibility.swift; sourceTree = ""; }; C06A7587296ECD75006B69A2 /* Theme+VisitorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+VisitorCode.swift"; sourceTree = ""; }; - C07FA04129AF550500E9FB7F /* ScreenSharingView.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSharingView.Mock.swift; sourceTree = ""; }; - C07FA04429AF55F600E9FB7F /* ScreenSharingViewController.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewController.Mock.swift; sourceTree = ""; }; + C07F62452ABC322B003EFC97 /* OrientationManager.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationManager.Mock.swift; sourceTree = ""; }; + C07F62762AC1BA2B003EFC97 /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = ""; }; + C07F62782AC2D2E8003EFC97 /* BackgroundSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundSwiftUI.swift; sourceTree = ""; }; + C07F627C2AC2F31F003EFC97 /* ScreenSharingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewModel.swift; sourceTree = ""; }; + C07F62802AC3057C003EFC97 /* ScreenSharingViewModel.mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewModel.mock.swift; sourceTree = ""; }; + C07F62822AC33BB9003EFC97 /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; C07FA04929AF83A400E9FB7F /* ActionButton.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButton.Mock.swift; sourceTree = ""; }; - C07FA04C29B0E41A00E9FB7F /* ScreenShareViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenShareViewControllerTests.swift; sourceTree = ""; }; - C07FA04D29B0E41A00E9FB7F /* VideoCallViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCallViewControllerTests.swift; sourceTree = ""; }; - C07FA04E29B0E41A00E9FB7F /* VisitorCodeViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisitorCodeViewControllerTests.swift; sourceTree = ""; }; + C07FA04C29B0E41A00E9FB7F /* ScreenShareViewControllerVoiceOverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenShareViewControllerVoiceOverTests.swift; sourceTree = ""; }; + C07FA04D29B0E41A00E9FB7F /* VideoCallViewControllerVoiceOverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCallViewControllerVoiceOverTests.swift; sourceTree = ""; }; + C07FA04E29B0E41A00E9FB7F /* VisitorCodeViewControllerVoiceOverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisitorCodeViewControllerVoiceOverTests.swift; sourceTree = ""; }; C0857DE428D470C1008D171D /* Theme+Shadow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Shadow.swift"; sourceTree = ""; }; C0857DE628D470F2008D171D /* Theme+Layer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Layer.swift"; sourceTree = ""; }; C0857DEA28D482D0008D171D /* Theme+Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Button.swift"; sourceTree = ""; }; @@ -1370,6 +1389,9 @@ C0D2F08629A4E8AE00803B47 /* CallButtonBar.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallButtonBar.Mock.swift; sourceTree = ""; }; C0D2F08929A4E92D00803B47 /* ConnectView.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectView.Mock.swift; sourceTree = ""; }; C0D2F08D29A61A7800803B47 /* VideoCallViewController.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallViewController.Mock.swift; sourceTree = ""; }; + C0E948032AB1D5D200890026 /* ActionButtonSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonSwiftUI.swift; sourceTree = ""; }; + C0E948052AB1D64700890026 /* HeaderButtonSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderButtonSwiftUI.swift; sourceTree = ""; }; + C0E948082AB1D6AB00890026 /* HeaderSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderSwiftUI.swift; sourceTree = ""; }; C4119E05268F41D1004DFEFB /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; C42463732673ABE10082C135 /* ScreenShareHandler.Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenShareHandler.Interface.swift; sourceTree = ""; }; C43C12F82694B14900C37E1B /* GliaPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GliaPresenter.swift; sourceTree = ""; }; @@ -1642,6 +1664,7 @@ 1A205D5A25655CB1003AA3CD /* GliaWidgets */ = { isa = PBXGroup; children = ( + C06152D22AB1BBEF00063BF8 /* SwiftUI */, 313EBD53294310EE008E9597 /* SecureConversations */, 7594091329891C48008B173A /* Public */, 1A60AFC12566857200E53F53 /* Sources */, @@ -1653,6 +1676,9 @@ 1A205D5C25655CB1003AA3CD /* Info.plist */, 1A60AFAE256680EF00E53F53 /* L10n.swift */, 31E35AB92A8648E9006EC7FB /* Localization.swift */, + 3146C9482AB18AC70047D8CC /* Localization+StringProviding.swift */, + 31882C9A2AA21B71009DE4BD /* Localization+Templates.swift */, + 31B1F8A82AB093ED009EC5AD /* StringProviding.swift */, 31DB0C00287C2EFC00FB288E /* StaticValues.swift */, ); path = GliaWidgets; @@ -1661,7 +1687,7 @@ 1A205D6525655CB2003AA3CD /* GliaWidgetsTests */ = { isa = PBXGroup; children = ( - 7552DFB22A6FBC6E0093519B /* CoreSdk */, + 3146C9412AB1850A0047D8CC /* Resources */, 7552DFAF2A6FB37E0093519B /* ChatMessage */, 846A5C3729D18D220049B29F /* ScreenShareHandler */, AFEF5C7229929A73005C3D8D /* SecureConversations */, @@ -2564,6 +2590,14 @@ path = ChatTranscript; sourceTree = ""; }; + 3146C9412AB1850A0047D8CC /* Resources */ = { + isa = PBXGroup; + children = ( + 3146C9422AB1851C0047D8CC /* LocalizationTests.swift */, + ); + path = Resources; + sourceTree = ""; + }; 315BAB1829ADFE9E00FF284B /* ConfirmationStyle */ = { isa = PBXGroup; children = ( @@ -2651,13 +2685,6 @@ path = ChatMessage; sourceTree = ""; }; - 7552DFB22A6FBC6E0093519B /* CoreSdk */ = { - isa = PBXGroup; - children = ( - ); - path = CoreSdk; - sourceTree = ""; - }; 755D186329A6A4B10009F5E8 /* WelcomeStyle */ = { isa = PBXGroup; children = ( @@ -2886,6 +2913,7 @@ children = ( 75940978298D38C2008B173A /* CallVisualizer.Environment.Mock.swift */, 75940979298D38C2008B173A /* CallVisualizer.VisitorCodeViewModel.Mock.swift */, + C07F62802AC3057C003EFC97 /* ScreenSharingViewModel.mock.swift */, ); path = Mock; sourceTree = ""; @@ -2955,8 +2983,6 @@ children = ( 84265E0B298AECBA00D65842 /* ScreenSharingViewStyle+Mock.swift */, C07FA04929AF83A400E9FB7F /* ActionButton.Mock.swift */, - C07FA04129AF550500E9FB7F /* ScreenSharingView.Mock.swift */, - C07FA04429AF55F600E9FB7F /* ScreenSharingViewController.Mock.swift */, ); path = Mocks; sourceTree = ""; @@ -2974,26 +3000,15 @@ 84265E4D298D7B2900D65842 /* ScreenSharing */ = { isa = PBXGroup; children = ( - 84265E4E298D7B2900D65842 /* ViewModel */, 84265E52298D7B2900D65842 /* Coordinator */, - 84265E56298D7B2900D65842 /* ScreenSharingViewStyle.swift */, - 84265E57298D7B2900D65842 /* ScreenSharingViewStyle.Accessibility.swift */, + C07F627A2AC2F2F4003EFC97 /* Model */, + C07F627B2AC2F301003EFC97 /* Style */, 84265E58298D7B2900D65842 /* View */, 84265E5A298D7B2900D65842 /* ViewController */, ); path = ScreenSharing; sourceTree = ""; }; - 84265E4E298D7B2900D65842 /* ViewModel */ = { - isa = PBXGroup; - children = ( - 84265E4F298D7B2900D65842 /* ScreenSharingViewModel+Environment.swift */, - 84265E50298D7B2900D65842 /* ScreenSharingViewModel.swift */, - 84265E51298D7B2900D65842 /* ScreenSharingViewModel+Output.swift */, - ); - path = ViewModel; - sourceTree = ""; - }; 84265E52298D7B2900D65842 /* Coordinator */ = { isa = PBXGroup; children = ( @@ -3015,7 +3030,6 @@ 84265E5A298D7B2900D65842 /* ViewController */ = { isa = PBXGroup; children = ( - 84265E5B298D7B2900D65842 /* ScreenSharingViewController+Props.swift */, 84265E5C298D7B2900D65842 /* ScreenSharingViewController.swift */, ); path = ViewController; @@ -3141,6 +3155,7 @@ 7512A57927BF9FCD00319DF1 /* ChatViewModelTests.swift */, 84681A942A61844000DD7406 /* ChatViewModelTests+Gva.swift */, 8491AF642AAB281500CC3E72 /* ChatViewModelTests+CustomCard.swift */, + 8491AF662AB8707600CC3E72 /* ChatViewModelTests+Transferring.swift */, ); path = ChatViewModel; sourceTree = ""; @@ -3324,7 +3339,8 @@ 9A1992CE27D61F5400161AAE /* SnapshotTests */ = { isa = PBXGroup; children = ( - 846E822728996A5C008EFBF0 /* AlertViewControllerTests.swift */, + C07F62752AC1BA0E003EFC97 /* extensions */, + 846E822728996A5C008EFBF0 /* AlertViewControllerVoiceOverTests.swift */, AF22C8842A6154780004BF3C /* AlertViewControllerLayoutTests.swift */, AF03A7AE2A6E7DC40081887D /* AlertViewControllerDynamicTypeFontTests.swift */, 9AB3401227F71D5D006E0FE2 /* BubbleViewVoiceOverTests.swift */, @@ -3339,22 +3355,23 @@ 9A1992D627D61F8100161AAE /* ChatViewControllerVoiceOverTests.swift */, AF22C88C2A6188EF0004BF3C /* ChatViewControllerLayoutTests.swift */, AF03A7B62A6EAFBA0081887D /* ChatViewControllerDynamicTypeFontTests.swift */, - C07FA04C29B0E41A00E9FB7F /* ScreenShareViewControllerTests.swift */, + C07FA04C29B0E41A00E9FB7F /* ScreenShareViewControllerVoiceOverTests.swift */, AF22C88E2A618B9D0004BF3C /* ScreenShareViewControllerLayoutTests.swift */, AF03A7B82A6ED1240081887D /* ScreenShareViewControllerDynamicTypeFontTests.swift */, - 75CF8D9029C3A85C00CB1524 /* SecureConversationsWelcomeScreenTests.swift */, + 75CF8D9029C3A85C00CB1524 /* SecureConversationsWelcomeScreenVoiceOverTests.swift */, AF22C8902A6198FF0004BF3C /* SecureConversationsWelcomeScreenLayoutTests.swift */, AF03A7BA2A6ED73E0081887D /* SecureConversationsWelcomeScreenDynamicTypeFontTests.swift */, - 75CF8DAC29C8F2B500CB1524 /* SecureConversationsConfirmationScreenTests.swift */, + 75CF8DAC29C8F2B500CB1524 /* SecureConversationsConfirmationScreenVoiceOverTests.swift */, AF22C8922A619F5C0004BF3C /* SecureConversationsConfirmationScreenLayoutTests.swift */, AF03A7BC2A6EDACF0081887D /* SecureConversationsConfirmationScreenDynamicTypeFontTests.swift */, 9A1992D727D61F8100161AAE /* SnapshotTestCase.swift */, 8458769E2823FD18007AC3DF /* SurveyViewControllerVoiceOverTests.swift */, AF22C8942A61A6B80004BF3C /* SurveyViewControllerLayoutTests.swift */, AF03A7BE2A6EDCBF0081887D /* SurveyViewControllerDynamicTypeFontTests.swift */, - C07FA04D29B0E41A00E9FB7F /* VideoCallViewControllerTests.swift */, + C07FA04D29B0E41A00E9FB7F /* VideoCallViewControllerVoiceOverTests.swift */, AF22C8962A61A9BF0004BF3C /* VideoCallViewControllerLayoutTests.swift */, AF03A7C02A6EDE190081887D /* VideoCallViewControllerDynamicTypeFontTests.swift */, + C07FA04E29B0E41A00E9FB7F /* VisitorCodeViewControllerVoiceOverTests.swift */, C07FA04E29B0E41A00E9FB7F /* VisitorCodeViewControllerTests.swift */, AF755FD92A71583900871E36 /* BubbleWindowLayoutTests.swift */, AF22C8982A61AE930004BF3C /* VisitorCodeViewControllerLayoutTests.swift */, @@ -3501,6 +3518,33 @@ path = ProximityManager; sourceTree = ""; }; + C06152D22AB1BBEF00063BF8 /* SwiftUI */ = { + isa = PBXGroup; + children = ( + C0E948012AB1D5B100890026 /* Components */, + C06152D82AB1BC2F00063BF8 /* Managers */, + C06152D32AB1BBFD00063BF8 /* Extensions */, + ); + path = SwiftUI; + sourceTree = ""; + }; + C06152D32AB1BBFD00063BF8 /* Extensions */ = { + isa = PBXGroup; + children = ( + C06152D42AB1BC1300063BF8 /* Font+Extensions.swift */, + AF18F1D42ABD954500121627 /* View+Accessibility.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + C06152D82AB1BC2F00063BF8 /* Managers */ = { + isa = PBXGroup; + children = ( + C07F62442ABC3218003EFC97 /* OrientationManager */, + ); + path = Managers; + sourceTree = ""; + }; C06A757D296EC743006B69A2 /* VisitorCode */ = { isa = PBXGroup; children = ( @@ -3519,6 +3563,41 @@ path = NumberSlot; sourceTree = ""; }; + C07F62442ABC3218003EFC97 /* OrientationManager */ = { + isa = PBXGroup; + children = ( + C06152D92AB1BC4300063BF8 /* OrientationManager.swift */, + C07F62452ABC322B003EFC97 /* OrientationManager.Mock.swift */, + ); + path = OrientationManager; + sourceTree = ""; + }; + C07F62752AC1BA0E003EFC97 /* extensions */ = { + isa = PBXGroup; + children = ( + C07F62762AC1BA2B003EFC97 /* UIViewController+Extensions.swift */, + C07F62822AC33BB9003EFC97 /* UIView+Extensions.swift */, + ); + path = extensions; + sourceTree = ""; + }; + C07F627A2AC2F2F4003EFC97 /* Model */ = { + isa = PBXGroup; + children = ( + C07F627C2AC2F31F003EFC97 /* ScreenSharingViewModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + C07F627B2AC2F301003EFC97 /* Style */ = { + isa = PBXGroup; + children = ( + 84265E56298D7B2900D65842 /* ScreenSharingViewStyle.swift */, + 84265E57298D7B2900D65842 /* ScreenSharingViewStyle.Accessibility.swift */, + ); + path = Style; + sourceTree = ""; + }; C096B408297EBCEB00F0C552 /* CallVisualizer */ = { isa = PBXGroup; children = ( @@ -3644,6 +3723,33 @@ path = Mocks; sourceTree = ""; }; + C0E948012AB1D5B100890026 /* Components */ = { + isa = PBXGroup; + children = ( + C0E948072AB1D69C00890026 /* Header */, + C0E948022AB1D5BC00890026 /* Buttons */, + C07F62782AC2D2E8003EFC97 /* BackgroundSwiftUI.swift */, + ); + path = Components; + sourceTree = ""; + }; + C0E948022AB1D5BC00890026 /* Buttons */ = { + isa = PBXGroup; + children = ( + C0E948032AB1D5D200890026 /* ActionButtonSwiftUI.swift */, + C0E948052AB1D64700890026 /* HeaderButtonSwiftUI.swift */, + ); + path = Buttons; + sourceTree = ""; + }; + C0E948072AB1D69C00890026 /* Header */ = { + isa = PBXGroup; + children = ( + C0E948082AB1D6AB00890026 /* HeaderSwiftUI.swift */, + ); + path = Header; + sourceTree = ""; + }; C42463722673ABCD0082C135 /* Screensharing */ = { isa = PBXGroup; children = ( @@ -4157,7 +4263,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "make write-diff\n"; + shellScript = "#make write-diff\n"; }; A5633E9F76E68066D5BFAF62 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -4222,6 +4328,7 @@ 1A6075E7258220E300569B0E /* UserImageStyle.swift in Sources */, 1ABD6C5D25B59D1C00D56EFA /* BubbleWindow.swift in Sources */, 75940964298D3889008B173A /* MessageRenderer.Web.swift in Sources */, + C0E948042AB1D5D200890026 /* ActionButtonSwiftUI.swift in Sources */, 1AE15E38257A578B00A642C0 /* MessageAlertConfiguration.swift in Sources */, C0175A132A56E29E001FACDE /* ChatMessageCardType.swift in Sources */, AFBBF5782851C391004993B3 /* Glia.Deprecated.swift in Sources */, @@ -4284,6 +4391,7 @@ 846A5C4029ED83C50049B29F /* CallVisualizer.Coordinator.DelegateEvent.swift in Sources */, 845876B4282AA296007AC3DF /* ButtonView.Props.Accessibility.swift in Sources */, 1A0C9AE025C9624500815406 /* ObservableValue.swift in Sources */, + 31B1F8A92AB093ED009EC5AD /* StringProviding.swift in Sources */, 75940962298D3889008B173A /* MessageMetadata.swift in Sources */, 7594094D298D37E8008B173A /* Glia.Environment.Mock.swift in Sources */, 1A60AFB9256682AF00E53F53 /* FlowCoordinator.swift in Sources */, @@ -4294,9 +4402,11 @@ 8491AF132A7ACC5400CC3E72 /* Theme.OperatorChatMessageStyle.swift in Sources */, 845E2F93283FB6D000C04D56 /* Theme.Survey.Accessibility.swift in Sources */, 75940959298D386F008B173A /* UIStackView.Extensions.swift in Sources */, + C07F627D2AC2F31F003EFC97 /* ScreenSharingViewModel.swift in Sources */, 1A0C9A9125C41AB900815406 /* CallButtonBar.swift in Sources */, C0D2F0302991229F00803B47 /* VideoCallCoordinator.Environment.swift in Sources */, 1AE15E3B257A5CC900A642C0 /* AlertConfiguration.swift in Sources */, + C07F62792AC2D2E8003EFC97 /* BackgroundSwiftUI.swift in Sources */, 75AF8D1027DFF4B3009EEE2C /* Survey.ValidationErrorView.swift in Sources */, 75940986298D38C2008B173A /* VisitorCodeView.swift in Sources */, 9A8130B927D757F900220BBD /* LocalFile.Mock.swift in Sources */, @@ -4312,6 +4422,7 @@ 9AE0A7622822AF3000725946 /* FontScaling.Environment.Live.swift in Sources */, 1A0452DD25DBD0A4000DA0C1 /* HeaderButton.swift in Sources */, AF3D520B2983235C00AD8E69 /* FileUploader.Mock.swift in Sources */, + C0E948092AB1D6AB00890026 /* HeaderSwiftUI.swift in Sources */, AF39330B29B2A6A00008B60D /* ChatViewModel.SecureConverstaions.swift in Sources */, 75AF8CEF27DAA819009EEE2C /* SurveyViewController.swift in Sources */, 1A60AF96256675C400E53F53 /* UIColor+Extensions.swift in Sources */, @@ -4332,6 +4443,7 @@ 9A3E1D9427B6C29C005634EB /* ChatEngagementFile.Mock.swift in Sources */, 1A2DA73B25EFC00500032611 /* FileUploadListStyle.swift in Sources */, 7594095A298D386F008B173A /* NSLayoutConstraint+Extensions.swift in Sources */, + C07F62462ABC322B003EFC97 /* OrientationManager.Mock.swift in Sources */, C0D2F06829A4B71C00803B47 /* VideoCallViewModel.Mock.swift in Sources */, AFEF5C6F29928DB0005C3D8D /* SecureConversations.FileUploadView.swift in Sources */, 84D5B9622A14F33400807F92 /* QuickLookBased.Live.swift in Sources */, @@ -4368,19 +4480,17 @@ 1A0452F025DBE268000DA0C1 /* MessageButtonStyle.swift in Sources */, EB2CBB1227D89F7D004F178E /* OnHoldOverlayStyle.swift in Sources */, 75940983298D38C2008B173A /* VisitorCodeViewModel+Delegate.swift in Sources */, - 84265E66298D7B2900D65842 /* ScreenSharingViewController+Props.swift in Sources */, C07FA04B29AF83B900E9FB7F /* ActionButton.Mock.swift in Sources */, C0D2F08C29A4EBA900803B47 /* VIdeoCallView.Environment.Mock.swift in Sources */, 313EBD552943116E008E9597 /* SecureConversations.swift in Sources */, - C07FA04329AF551D00E9FB7F /* ScreenSharingView.Mock.swift in Sources */, 7529F2B627E1EB9A004D3581 /* Survey.ButtonView.swift in Sources */, + C07F62812AC3057C003EFC97 /* ScreenSharingViewModel.mock.swift in Sources */, C49A29E42614A29700819269 /* FilePreviewView.swift in Sources */, 1A5F815F258A43E600A605DA /* Section.swift in Sources */, 1A60AFBF2566834400E53F53 /* BaseViewController.swift in Sources */, 6E60DD5627146C9D001422EF /* AlertViewController+SingleAction.swift in Sources */, 845E2F70283CF94100C04D56 /* VisitorChatMessageStyle.Accessibility.swift in Sources */, 8491AF4E2A9CB3A400CC3E72 /* SecureConversations.TranscriptModel+GVA.swift in Sources */, - 84265E5F298D7B2900D65842 /* ScreenSharingViewModel+Output.swift in Sources */, 8491AF062A77F16D00CC3E72 /* GvaGalleryCardStyle.swift in Sources */, 1A60AFF12566A4B300E53F53 /* NavigationPresenter.swift in Sources */, 1AC7A74F2582571100567FF8 /* Interactor.swift in Sources */, @@ -4411,6 +4521,7 @@ 9A19926B27D3BA8700161AAE /* ViewFactory.Environment.Mock.swift in Sources */, 84681A9B2A669D8800DD7406 /* QuickReplyView.swift in Sources */, 1A6EB05725A717CB0007081A /* ChatMessage.swift in Sources */, + C0E948062AB1D64700890026 /* HeaderButtonSwiftUI.swift in Sources */, 1AA738B225790D5A00E1120F /* AlertView.swift in Sources */, 845E2F8E283FB5B500C04D56 /* Theme.Survey.SingleQuestion.Accessibility.swift in Sources */, C47901B725ED2FB0007EE195 /* AlertViewController+ScreenShareOffer.swift in Sources */, @@ -4443,6 +4554,7 @@ 845876B1282A9EAF007AC3DF /* CheckboxView.Props.Accessibility.swift in Sources */, 1A4AF5CD25AEF4A0002CD0F4 /* AlertViewController+Message.swift in Sources */, AFCF8A5C2A02AB3000B7ABB3 /* OutgoingMessage.Mock.swift in Sources */, + AF18F1D52ABD954500121627 /* View+Accessibility.swift in Sources */, C0D2F08F29A61A8D00803B47 /* VideoCallViewController.Mock.swift in Sources */, 1A7CA8242574D68E0047CBBE /* ConnectStatusView.swift in Sources */, 845E2F78283E2D4200C04D56 /* PoweredByStyle.swift in Sources */, @@ -4483,12 +4595,12 @@ 7594093E298D376B008B173A /* RemoteConfiguration.swift in Sources */, 1AC7A7AF258786CB00567FF8 /* EngagementViewModel.swift in Sources */, 9AE9E4B327E0E60F00BFE239 /* CallViewModel.Mock.swift in Sources */, + 31882C9B2AA21B71009DE4BD /* Localization+Templates.swift in Sources */, 845876A8282A94CF007AC3DF /* BooleanQuestionView.Props.Accessibility.swift in Sources */, 1A6EBB2525ADE36900EE325D /* MediaUpgradePresenter.swift in Sources */, 1AA738B925790DB400E1120F /* ActionButtonStyle.swift in Sources */, C06A7582296EC856006B69A2 /* NumberSlotStyle.swift in Sources */, 1A2DA71F25EF720400032611 /* FilePickerViewModel.swift in Sources */, - 84265E5D298D7B2900D65842 /* ScreenSharingViewModel+Environment.swift in Sources */, 750C0ADD2850D53F003E0415 /* Theme.Survey.ScaleQuestion.swift in Sources */, 9A1992E927D6BCD700161AAE /* FileDownload.Mock.swift in Sources */, AF3D520D2983B3DD00AD8E69 /* FileUploader.Environment.Interface.swift in Sources */, @@ -4516,6 +4628,7 @@ 9AB196DE27C3FFF400FD60AB /* Call.Environment.Mock.swift in Sources */, 1A0C143125B8547200B00695 /* EngagementStyle.swift in Sources */, 7594098B298D38C2008B173A /* CallVisualizer+Presentation.swift in Sources */, + C06152DA2AB1BC4300063BF8 /* OrientationManager.swift in Sources */, 9A186A3727F5D38D0055886D /* ChatMessageEntryStyle.Accessibility.swift in Sources */, 9AB061C1280EFE09008960FA /* ChatFileDownloadStyle.Accessibility.swift in Sources */, 1A7CA8272574D6F40047CBBE /* ConnectStyle.swift in Sources */, @@ -4549,6 +4662,7 @@ 3100EEF2293E214B00D57F71 /* SecureConversations.Coordinator.swift in Sources */, 1AA738AE2578E0D500E1120F /* ConnectAnimationView.swift in Sources */, 754CC61627E2816F005676E9 /* Survey.InputQuestionView.swift in Sources */, + 3146C9492AB18AC70047D8CC /* Localization+StringProviding.swift in Sources */, 9A1992DF27D62C2E00161AAE /* ImageView.Cache.Mock.swift in Sources */, 8491AF0D2A7A9CB900CC3E72 /* Theme.VisitorChatMessageStyle.swift in Sources */, 1A2DA72D25EF9DD900032611 /* FileUpload.swift in Sources */, @@ -4597,7 +4711,6 @@ 3197F7B829F7C318008EE9F7 /* SecureConversations.CommonEngagementModel.swift in Sources */, 31DD41652A57105400F92612 /* SecureConversations.TranscriptModel.Environment.swift in Sources */, 1A277A1225FA604E009FE131 /* ChatFileContentView.swift in Sources */, - 84265E5E298D7B2900D65842 /* ScreenSharingViewModel.swift in Sources */, 75B7BD802A39D5A70060794D /* Layoutable.swift in Sources */, 845876AB282A959C007AC3DF /* SingleChoiceQuestionView.Props.Accessibility.swift in Sources */, 9AB3402327FC859E006E0FE2 /* CallButtonStyle.StateStyle.Accessibility.swift in Sources */, @@ -4607,6 +4720,7 @@ 6E9C01AD26D3B8B500EBE1D4 /* OperatorTypingIndicatorView.swift in Sources */, AF10ED8B29B7A4C000E85309 /* ChatViewModel+ChoiceCards.swift in Sources */, 7594097E298D38C2008B173A /* CallVisualizer.BubbleIcon.swift in Sources */, + C06152D52AB1BC1300063BF8 /* Font+Extensions.swift in Sources */, AFA2FDF628907FC800428E6D /* BubbleStyle.Mock.swift in Sources */, 755D186B29A6A5830009F5E8 /* WelcomeStyle+MessageTitleStyle.swift in Sources */, 75940981298D38C2008B173A /* VisitorCodeView+NumberView.swift in Sources */, @@ -4684,7 +4798,6 @@ 846A5C3629CB3E270049B29F /* ScreenShareHandler.Mock.swift in Sources */, C0D2F03B299149D600803B47 /* VideoCallViewModel.swift in Sources */, C0D2F02E2991221900803B47 /* VideoCallCoordinator.DelegateEvent.swift in Sources */, - C07FA04629AF560A00E9FB7F /* ScreenSharingViewController.Mock.swift in Sources */, 1A2DA73125EFA77E00032611 /* FileUploader.swift in Sources */, 1A60B0272568070800E53F53 /* UIStackView+Extensions.swift in Sources */, 75940945298D378A008B173A /* CoreSDKClient.Mock.swift in Sources */, @@ -4781,12 +4894,14 @@ 84681A952A61844000DD7406 /* ChatViewModelTests+Gva.swift in Sources */, 847A7643285A1914004044D1 /* FileUploadListViewModelTests.swift in Sources */, 9A1992E727D66C7400161AAE /* UIKitBased.Failing.swift in Sources */, + 3146C9432AB1851C0047D8CC /* LocalizationTests.swift in Sources */, 846A5C3929D18D400049B29F /* ScreenShareHandlerTests.swift in Sources */, 9AE05CB62805D2CB00871321 /* Interactor.Environment.Failing.swift in Sources */, 846429862A45DB4100943BD6 /* AlertViewController.Kind+Mock.swift in Sources */, AF29811229E42F3C0005BD55 /* AvailabilityTests.swift in Sources */, 9AE05CB32805C9D900871321 /* ChatViewModel.Environment.Failing.swift in Sources */, 7552DFB12A6FB7DF0093519B /* ChatMessageTests.swift in Sources */, + 8491AF672AB8707600CC3E72 /* ChatViewModelTests+Transferring.swift in Sources */, AFEF5C7429929A8D005C3D8D /* SecureConversations.FileUploadListViewModel.Environment.Failing.swift in Sources */, 9A3E1D9D27BA7741005634EB /* FoundationBased.Failing.swift in Sources */, 9A3E1D8427B67F1B005634EB /* Helper.swift in Sources */, @@ -4851,17 +4966,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 846E822828996A5C008EFBF0 /* AlertViewControllerTests.swift in Sources */, + 846E822828996A5C008EFBF0 /* AlertViewControllerVoiceOverTests.swift in Sources */, AF03A7BB2A6ED73E0081887D /* SecureConversationsWelcomeScreenDynamicTypeFontTests.swift in Sources */, - 75CF8D9129C3A85C00CB1524 /* SecureConversationsWelcomeScreenTests.swift in Sources */, + 75CF8D9129C3A85C00CB1524 /* SecureConversationsWelcomeScreenVoiceOverTests.swift in Sources */, AF22C8972A61A9BF0004BF3C /* VideoCallViewControllerLayoutTests.swift in Sources */, + C07F62772AC1BA2B003EFC97 /* UIViewController+Extensions.swift in Sources */, AF03A7BD2A6EDACF0081887D /* SecureConversationsConfirmationScreenDynamicTypeFontTests.swift in Sources */, AF03A7B92A6ED1240081887D /* ScreenShareViewControllerDynamicTypeFontTests.swift in Sources */, + C07F62832AC33BB9003EFC97 /* UIView+Extensions.swift in Sources */, 9AE9E4B527E0EE2E00BFE239 /* CallViewControllerVoiceOverTests.swift in Sources */, AF03A7BF2A6EDCBF0081887D /* SurveyViewControllerDynamicTypeFontTests.swift in Sources */, AF22C8852A6154780004BF3C /* AlertViewControllerLayoutTests.swift in Sources */, AF22C8872A6182AF0004BF3C /* BubbleViewLayoutTests.swift in Sources */, AF03A7C12A6EDE190081887D /* VideoCallViewControllerDynamicTypeFontTests.swift in Sources */, + C07FA05029B0E41A00E9FB7F /* VideoCallViewControllerVoiceOverTests.swift in Sources */, C07FA05029B0E41A00E9FB7F /* VideoCallViewControllerTests.swift in Sources */, AF755FDA2A71583900871E36 /* BubbleWindowLayoutTests.swift in Sources */, AF22C88D2A6188EF0004BF3C /* ChatViewControllerLayoutTests.swift in Sources */, @@ -4875,10 +4993,10 @@ 9A1992D827D61F8100161AAE /* ChatViewControllerVoiceOverTests.swift in Sources */, AF22C8892A6184C50004BF3C /* CallViewControllerLayoutTests.swift in Sources */, 8458769F2823FD18007AC3DF /* SurveyViewControllerVoiceOverTests.swift in Sources */, - C07FA05129B0E41A00E9FB7F /* VisitorCodeViewControllerTests.swift in Sources */, - 75CF8DAD29C8F2B500CB1524 /* SecureConversationsConfirmationScreenTests.swift in Sources */, + C07FA05129B0E41A00E9FB7F /* VisitorCodeViewControllerVoiceOverTests.swift in Sources */, + 75CF8DAD29C8F2B500CB1524 /* SecureConversationsConfirmationScreenVoiceOverTests.swift in Sources */, 9AB3402928002422006E0FE2 /* ChatCallUpgradeViewVoiceOverTests.swift in Sources */, - C07FA04F29B0E41A00E9FB7F /* ScreenShareViewControllerTests.swift in Sources */, + C07FA04F29B0E41A00E9FB7F /* ScreenShareViewControllerVoiceOverTests.swift in Sources */, AF22C8952A61A6B80004BF3C /* SurveyViewControllerLayoutTests.swift in Sources */, 9A1992D927D61F8100161AAE /* SnapshotTestCase.swift in Sources */, AF03A7B52A6EAA950081887D /* ChatCallUpgradeViewDynamicTypeFontTests.swift in Sources */, diff --git a/GliaWidgets/Color.swift b/GliaWidgets/Color.swift index 0f92d83b9..b2fc2b771 100644 --- a/GliaWidgets/Color.swift +++ b/GliaWidgets/Color.swift @@ -8,7 +8,5 @@ enum Color { static let baseNormal = UIColor(hex: 0x6C7683) // grey static let baseShade = UIColor(hex: 0x6C7683, alpha: 0.5) static let baseDark = UIColor(hex: 0x2C0735) // purple - static let lightGrey = UIColor(hex: 0xF3F3F3) // light gray - static let grey = UIColor(hex: 0x999999) // gray - static let background: UIColor = .white + static let baseNeutral = UIColor(hex: 0xF3F3F3) // light gray } diff --git a/GliaWidgets/Info.plist b/GliaWidgets/Info.plist index 76778b66f..381987770 100644 --- a/GliaWidgets/Info.plist +++ b/GliaWidgets/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.1.0 + 2.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/GliaWidgets/Localization+StringProviding.swift b/GliaWidgets/Localization+StringProviding.swift new file mode 100644 index 000000000..0e79e20c3 --- /dev/null +++ b/GliaWidgets/Localization+StringProviding.swift @@ -0,0 +1,22 @@ +import Foundation + +extension Localization { + static func tr( + _ table: String, + _ key: String, + _ args: CVarArg..., + fallback value: String, + stringProviding: StringProviding? = Glia.sharedInstance.stringProviding, + bundleManaging: BundleManaging = .live + ) -> String { + guard + let stringProviding, + let remoteString = stringProviding.getRemoteString(key) + else { + let format = bundleManaging.current().localizedString(forKey: key, value: value, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } + + return remoteString + } +} diff --git a/GliaWidgets/Localization+Templates.swift b/GliaWidgets/Localization+Templates.swift new file mode 100644 index 000000000..b011d413e --- /dev/null +++ b/GliaWidgets/Localization+Templates.swift @@ -0,0 +1,14 @@ +import Foundation + +extension Localization { + enum Templates { + static let percentValue = "{uploadPercentValue}%" + static let fileNameWithProgressValue = "{uploadedFileName}, {uploadPercentValue}%" + static let operatorName = "{operatorName}" + static let callDuration = "{callDuration}" + static let titleAndBadgeValue = "{buttonTitle}, {badgeValue}" + static let errorMessage = "{message}" + static let downloadWithFileState = "{downloadedFileName}, {downloadedFileState}" + static let downloadWithFileStateAndPercentValue = "{downloadedFileName}, {downloadedFileState} {downloadPercentValue}%" + } +} diff --git a/GliaWidgets/Localization.swift b/GliaWidgets/Localization.swift index 886467e80..c8f4390c9 100644 --- a/GliaWidgets/Localization.swift +++ b/GliaWidgets/Localization.swift @@ -15,26 +15,42 @@ internal enum Localization { /// Settings internal static let settings = 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 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 enum MicrophoneAccess { + /// Unable to access microphone + internal static let error = 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") + /// {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 enum Stop { + /// Stop Screen Sharing? + internal static let header = 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 enum Call { - /// On Hold - internal static let onHold = Localization.tr("Localizable", "call.on_hold", fallback: "On Hold") internal enum Bubble { internal enum Accessibility { - /// Deactivates minimize. - internal static let hint = Localization.tr("Localizable", "call.bubble.accessibility.hint", fallback: "Deactivates minimize.") - /// Operator Avatar - internal static let label = Localization.tr("Localizable", "call.bubble.accessibility.label", fallback: "Operator Avatar") + /// Expands call view. + internal static let hint = 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 enum Button { - /// Mute - internal static let mute = Localization.tr("Localizable", "call.button.mute", fallback: "Mute") - /// Speaker - internal static let speaker = Localization.tr("Localizable", "call.button.speaker", fallback: "Speaker") - /// Unmute - internal static let unmute = Localization.tr("Localizable", "call.button.unmute", fallback: "Unmute") - } internal enum Buttons { internal enum Chat { internal enum BadgeValue { @@ -67,48 +83,64 @@ internal enum Localization { } } } + internal enum Duration { + internal enum Accessibility { + /// Call duration. + internal static let label = Localization.tr("Localizable", "call.duration.accessibility.label", fallback: "Call duration.") + } + } internal enum Header { - internal enum Close { + internal enum Back { internal enum Button { internal enum Accessibility { - /// Activates minimize. - internal static let hint = Localization.tr("Localizable", "call.header.close.button.accessibility.hint", fallback: "Activates minimize.") + /// Minimizes call view. + internal static let hint = 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 enum OnHold { /// You can continue browsing while you are on hold - internal static let bottomText = Localization.tr("Localizable", "call.onHold.bottom_text", fallback: "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") + /// On Hold + internal static let icon = Localization.tr("Localizable", "call.on_hold.icon", fallback: "On Hold") } - internal enum Operator { - internal enum Avatar { - internal enum Accessibility { - /// Displays operator avatar or placeholder. - internal static let hint = Localization.tr("Localizable", "call.operator.avatar.accessibility.hint", fallback: "Displays operator avatar or placeholder.") - /// Operator Avatar - internal static let label = Localization.tr("Localizable", "call.operator.avatar.accessibility.label", fallback: "Operator Avatar") - } + 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.") + /// Operator Picture + internal static let label = Localization.tr("Localizable", "call.operator_avatar.accessibility.label", fallback: "Operator Picture") } } internal enum OperatorName { internal enum Accessibility { - /// Displays operator name. - internal static let hint = Localization.tr("Localizable", "call.operator_name.accessibility.hint", fallback: "Displays operator name.") + /// Shows operator name. + internal static let hint = Localization.tr("Localizable", "call.operator_name.accessibility.hint", fallback: "Shows operator name.") } } - internal enum Video { - internal enum Operator { - internal enum Accessibility { - /// Operator's Video - internal static let label = Localization.tr("Localizable", "call.video.operator.accessibility.label", fallback: "Operator's Video") - } + 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 enum Visitor { - internal enum Accessibility { - /// Your Video - internal static let label = Localization.tr("Localizable", "call.video.visitor.accessibility.label", fallback: "Your Video") - } + } + internal enum Speaker { + /// Speaker + internal static let button = 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 enum VisitorVideo { + internal enum Accessibility { + /// Your Video + internal static let label = Localization.tr("Localizable", "call.visitor_video.accessibility.label", fallback: "Your Video") } } } @@ -116,38 +148,32 @@ internal enum Localization { 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") - /// Screen Sharing - internal static let title = Localization.tr("Localizable", "call_visualizer.screen_sharing.title", fallback: "Screen Sharing") + internal enum Header { + /// Screen Sharing + internal static let title = 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 enum Action { - /// Close - internal static let close = Localization.tr("Localizable", "call_visualizer.visitor_code.action.close", fallback: "Close") - /// Refresh - internal static let refresh = Localization.tr("Localizable", "call_visualizer.visitor_code.action.refresh", fallback: "Refresh") - } internal enum Close { internal enum Accessibility { - /// Closes visitor code - internal static let hint = Localization.tr("Localizable", "call_visualizer.visitor_code.close.accessibility.hint", fallback: "Closes visitor code") - /// Close Button - internal static let label = Localization.tr("Localizable", "call_visualizer.visitor_code.close.accessibility.label", fallback: "Close Button") + /// Closes the visitor code + internal static let hint = Localization.tr("Localizable", "call_visualizer.visitor_code.close.accessibility.hint", fallback: "Closes the visitor code") } } internal enum Refresh { internal enum Accessibility { - /// Generates new visitor code - internal static let hint = Localization.tr("Localizable", "call_visualizer.visitor_code.refresh.accessibility.hint", fallback: "Generates new visitor code") + /// 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") /// Refresh Button internal static let label = Localization.tr("Localizable", "call_visualizer.visitor_code.refresh.accessibility.label", fallback: "Refresh Button") } } internal enum Title { internal enum Accessibility { - /// Your five-digit visitor code is - internal static let hint = Localization.tr("Localizable", "call_visualizer.visitor_code.title.accessibility.hint", fallback: "Your five-digit visitor code is") + /// 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.") } } } @@ -155,42 +181,72 @@ internal enum Localization { internal enum Chat { /// Pick attachment internal static let attachFiles = Localization.tr("Localizable", "chat.attach_files", fallback: "Pick attachment") - /// {operatorName} has joined the conversation. - internal static let operatorJoined = Localization.tr("Localizable", "chat.operator_joined", fallback: "{operatorName} has joined the conversation.") /// New Messages internal static let unreadMessageDivider = Localization.tr("Localizable", "chat.unread_message_divider", fallback: "New Messages") - internal enum Attachement { + internal enum Attachment { /// Photo Library - internal static let photoLibrary = Localization.tr("Localizable", "chat.attachement.photo_library", fallback: "Photo Library") + internal static let photoLibrary = Localization.tr("Localizable", "chat.attachment.photo_library", fallback: "Photo Library") /// Take Photo or Video - internal static let takePhoto = Localization.tr("Localizable", "chat.attachement.take_photo", fallback: "Take Photo or Video") - internal enum Upload { - /// Invalid file type! - internal static let unsupportedFile = Localization.tr("Localizable", "chat.attachement.upload.unsupported_file", fallback: "Invalid file type!") + internal static let takePhoto = 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 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 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 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 enum Image { + internal enum Accessibility { + /// Choice card + internal static let label = 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 enum Duration { - internal enum Accessibility { - /// Displays call duration. - internal static let label = Localization.tr("Localizable", "chat.duration.accessibility.label", fallback: "Displays call duration.") - } + /// Could not download the file. + internal static let failed = Localization.tr("Localizable", "chat.download.failed", fallback: "Could not download the file.") } internal enum File { - /// Failed to confirm the safety of the file. - internal static let infectedError = Localization.tr("Localizable", "chat.file.infected_error", fallback: "Failed to confirm the safety of the file.") - /// File size over 25MB limit! - internal static let tooLargeError = Localization.tr("Localizable", "chat.file.too_large_error", fallback: "File size over 25MB limit!") + 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 enum RemoveUpload { + internal enum Accessibility { + /// Remove upload + internal static let label = 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 enum Upload { - /// Uploading failed - internal static let failed = Localization.tr("Localizable", "chat.file.upload.failed", fallback: "Uploading failed") + /// Could not upload the file. + internal static let failed = 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.") /// Uploading file… internal static let inProgress = Localization.tr("Localizable", "chat.file.upload.in_progress", fallback: "Uploading file…") - /// Checking safety of the file… - internal static let scanning = Localization.tr("Localizable", "chat.file.upload.scanning", fallback: "Checking safety of the 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.") + /// Checking file security… + internal static let scanning = 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") } @@ -198,10 +254,20 @@ internal enum Localization { internal enum Input { /// Enter Message internal static let placeholder = Localization.tr("Localizable", "chat.input.placeholder", fallback: "Enter Message") - /// Send - internal static let send = Localization.tr("Localizable", "chat.input.send", fallback: "Send") + } + 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 enum Video { + /// Upgraded to Video + internal static let systemMessage = 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") /// 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 enum Unread { @@ -211,61 +277,53 @@ internal enum Localization { } } } - internal enum Operator { - internal enum Avatar { - internal enum Accessibility { - /// Avatar - internal static let label = Localization.tr("Localizable", "chat.operator.avatar.accessibility.label", fallback: "Avatar") - } - } - internal enum Name { - internal enum Accessibility { - /// Displays operator name. - internal static let label = Localization.tr("Localizable", "chat.operator.name.accessibility.label", fallback: "Displays operator name.") - } + internal enum OperatorAvatar { + internal enum Accessibility { + /// Operator Picture + internal static let label = Localization.tr("Localizable", "chat.operator_avatar.accessibility.label", fallback: "Operator Picture") } } - internal enum Status { - /// Delivered - internal static let delivered = Localization.tr("Localizable", "chat.status.delivered", fallback: "Delivered") - /// Operator typing - internal static let typing = Localization.tr("Localizable", "chat.status.typing", fallback: "Operator typing") + 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 enum Upgrade { - internal enum Audio { - /// Upgraded to Audio Call - internal static let text = Localization.tr("Localizable", "chat.upgrade.audio.text", fallback: "Upgraded to Audio Call") - } - internal enum Video { - /// Upgraded to Video Call - internal static let text = Localization.tr("Localizable", "chat.upgrade.video.text", fallback: "Upgraded to Video Call") + internal enum OperatorName { + internal enum Accessibility { + /// Operator Name + internal static let label = Localization.tr("Localizable", "chat.operator_name.accessibility.label", fallback: "Operator Name") } } - internal enum Upload { - internal enum Remove { + internal enum Status { + /// Operator is typing + internal static let typing = Localization.tr("Localizable", "chat.status.typing", fallback: "Operator is typing") + internal enum Typing { internal enum Accessibility { - /// Remove upload - internal static let label = Localization.tr("Localizable", "chat.upload.remove.accessibility.label", fallback: "Remove upload") + /// {operatorName} is typing + internal static let label = Localization.tr("Localizable", "chat.status.typing.accessibility.label", fallback: "{operatorName} is typing") } } } } internal enum Engagement { /// Operator - internal static let defaultOperatorName = Localization.tr("Localizable", "engagement.default_operator_name", fallback: "Operator") - /// Minimize - internal static let minimizeVideoButton = Localization.tr("Localizable", "engagement.minimize_video_button", fallback: "Minimize") - /// {operatorName} has offered you to upgrade - internal static let offerUpgrade = Localization.tr("Localizable", "engagement.offer_upgrade", fallback: "{operatorName} has offered you to upgrade") - internal enum Connect { - /// We are here to help - internal static let placeholder = Localization.tr("Localizable", "engagement.connect.placeholder", fallback: "We are here to help") + internal static let defaultOperator = 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 enum Chat { + /// Chat + internal static let title = Localization.tr("Localizable", "engagement.chat.title", fallback: "Chat") + } + internal enum ConnectionScreen { /// Connecting with {operatorName} - internal static let with = Localization.tr("Localizable", "engagement.connect.with", fallback: "Connecting with {operatorName}") + internal static let connectWith = 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 enum End { - /// Are you sure you want to end engagement? - internal static let message = Localization.tr("Localizable", "engagement.end.message", fallback: "Are you sure you want to end engagement?") + /// 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 enum Confirmation { /// End Engagement? internal static let header = Localization.tr("Localizable", "engagement.end.confirmation.header", fallback: "End Engagement?") @@ -278,26 +336,46 @@ internal enum Localization { /// Thank you! internal static let message = Localization.tr("Localizable", "engagement.ended.message", fallback: "This engagement has ended.\nThank you!") } - internal enum QueueClosed { - /// We're sorry - internal static let header = Localization.tr("Localizable", "engagement.queue_closed.header", fallback: "We're sorry") - /// 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 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 enum Audio { + /// Speak through your device + internal static let info = 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 enum QueueLeave { - /// 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?") - /// 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 enum MinimizeVideo { + /// Minimize + internal static let button = Localization.tr("Localizable", "engagement.minimize_video.button", fallback: "Minimize") } - internal enum QueueReconnectionFailed { - /// Please try again. - internal static let tryAgain = Localization.tr("Localizable", "engagement.queue_reconnection_failed.try_again", fallback: "Please try again.") + internal enum Phone { + /// Phone + internal static let title = Localization.tr("Localizable", "engagement.phone.title", fallback: "Phone") } - internal enum QueueTransferring { + internal enum Queue { /// Transferring - internal static let message = Localization.tr("Localizable", "engagement.queue_transferring.message", fallback: "Transferring") + internal static let transferring = 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.") + /// 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 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?") + /// 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 enum Reconnection { + /// Please try again later. + internal static let failed = 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. @@ -307,16 +385,18 @@ internal enum Localization { /// Messaging internal static let title = 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 enum Error { - /// Something went wrong - internal static let general = Localization.tr("Localizable", "error.general", fallback: "Something went wrong") - /// Internal error - internal static let `internal` = Localization.tr("Localizable", "error.internal", fallback: "Internal error") - internal enum Unexpected { - /// We're sorry, there has been an unexpected error. - internal static let title = Localization.tr("Localizable", "error.unexpected.title", fallback: "We're sorry, there has been an unexpected error.") - } + /// Something went wrong. + internal static let general = 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.") + /// Something went wrong. + internal static let unexpected = Localization.tr("Localizable", "error.unexpected", fallback: "Something went wrong.") } internal enum General { /// Accept @@ -331,8 +411,10 @@ internal enum Localization { internal static let close = Localization.tr("Localizable", "general.close", fallback: "Close") /// Comment internal static let comment = Localization.tr("Localizable", "general.comment", fallback: "Comment") - /// CompanyName - internal static let companyName = Localization.tr("Localizable", "general.company_name", fallback: "CompanyName") + /// Company Name + internal static let companyName = 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) /// Decline internal static let decline = Localization.tr("Localizable", "general.decline", fallback: "Decline") /// Download @@ -347,12 +429,18 @@ internal enum Localization { internal static let ok = Localization.tr("Localizable", "general.ok", fallback: "Ok") /// Open internal static let `open` = Localization.tr("Localizable", "general.open", fallback: "Open") - /// Powered by Glia - internal static let poweredBy = Localization.tr("Localizable", "general.powered_by", fallback: "Powered by Glia") + /// Powered by + internal static let powered = Localization.tr("Localizable", "general.powered", fallback: "Powered by") + /// Refresh + internal static let refresh = Localization.tr("Localizable", "general.refresh", fallback: "Refresh") /// Retry internal static let retry = Localization.tr("Localizable", "general.retry", fallback: "Retry") /// Selected internal static let selected = Localization.tr("Localizable", "general.selected", fallback: "Selected") + /// Send + internal static let send = Localization.tr("Localizable", "general.send", fallback: "Send") + /// Sending… + internal static let sending = Localization.tr("Localizable", "general.sending", fallback: "Sending…") /// Submit internal static let submit = Localization.tr("Localizable", "general.submit", fallback: "Submit") /// Thank you! @@ -361,37 +449,56 @@ internal enum Localization { internal static let yes = Localization.tr("Localizable", "general.yes", fallback: "Yes") /// You internal static let you = 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 enum Gva { - /// This action is not currently supported on mobile. - internal static let errorUnsupported = Localization.tr("Localizable", "gva.error_unsupported", fallback: "This action is not currently supported on mobile.") - } - internal enum Media { - internal enum Audio { - /// Audio - internal static let name = Localization.tr("Localizable", "media.audio.name", fallback: "Audio") + 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 enum Messaging { - /// Send a message and we’ll get back to you - /// within 48 hours - internal static let description = Localization.tr("Localizable", "media.messaging.description", fallback: "Send a message and we’ll get back to you \nwithin 48 hours") + } + 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 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 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 enum Phone { - /// Phone - internal static let name = Localization.tr("Localizable", "media.phone.name", fallback: "Phone") + 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 enum Text { - /// Chat - internal static let name = Localization.tr("Localizable", "media.text.name", fallback: "Chat") + } + 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 enum Video { - /// Video - internal static let name = Localization.tr("Localizable", "media.video.name", fallback: "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 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 enum MessageCenter { - /// Check messages - internal static let checkMessages = Localization.tr("Localizable", "message_center.check_messages", fallback: "Check messages") /// Messaging internal static let header = Localization.tr("Localizable", "message_center.header", fallback: "Messaging") internal enum Confirmation { @@ -417,10 +524,12 @@ internal enum Localization { internal static let title = Localization.tr("Localizable", "message_center.unavailable.title", fallback: "Message Center Unavailable") } internal enum Welcome { - /// The message cannot exceed 10000 characters. - internal static let messageLengthWarning = Localization.tr("Localizable", "message_center.welcome.message_length_warning", fallback: "The message cannot exceed 10000 characters.") + /// Check messages + internal static let checkMessages = 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") + /// 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.") /// Welcome to Message Center internal static let title = Localization.tr("Localizable", "message_center.welcome.title", fallback: "Welcome to Message Center") internal enum CheckMessages { @@ -433,17 +542,17 @@ internal enum Localization { 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 enum FilePickerLabel { - internal enum Accessibility { /// File picker - internal static let label = Localization.tr("Localizable", "message_center.welcome.file_picker_label.accessibility.label", fallback: "File picker") + internal static let label = Localization.tr("Localizable", "message_center.welcome.file_picker.accessibility.label", fallback: "File picker") } } - internal enum MessageTextView { + internal enum MessageInput { /// Enter your message - internal static let placeholder = Localization.tr("Localizable", "message_center.welcome.message_text_view.placeholder", fallback: "Enter your message") + internal static let placeholder = 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 enum Send { internal enum Accessibility { @@ -455,34 +564,34 @@ internal enum Localization { } internal enum ScreenSharing { internal enum VisitorScreen { - /// End screen sharing - internal static let end = Localization.tr("Localizable", "screen_sharing.visitor_screen.end", fallback: "End screen sharing") 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.") /// 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 enum Screensharing { - 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", "screensharing.visitor_screen.disclaimer.info", fallback: "Depending on your selection, your entire screen might be shared with the operator, not just the application window.") + internal enum End { + /// End Screen Sharing + internal static let title = 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 enum SendMessage { - /// Send message - internal static let send = Localization.tr("Localizable", "send_message.send", fallback: "Send message") - /// Sending… - internal static let sending = Localization.tr("Localizable", "send_message.sending", fallback: "Sending…") - } 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 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 enum OptionButton { internal enum Selected { internal enum Accessibility { @@ -497,44 +606,22 @@ internal enum Localization { } } } - internal enum TextField { - internal enum Accessibility { - /// Enter the answer - internal static let hint = Localization.tr("Localizable", "survey.question.text_field.accessibility.hint", fallback: "Enter the answer") - } - } - internal enum Title { + internal enum Required { internal enum Accessibility { - /// Required - internal static let label = Localization.tr("Localizable", "survey.question.title.accessibility.label", fallback: "Required") + /// This is a required question. + internal static let label = Localization.tr("Localizable", "survey.question.required.accessibility.label", fallback: "This is a required question.") } } } internal enum Validation { internal enum Title { internal enum Accessibility { - /// Please provide an answer for question above - internal static let label = Localization.tr("Localizable", "survey.validation.title.accessibility.label", fallback: "Please provide an answer for question above") + /// 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 enum Upgrade { - internal enum Audio { - /// {operatorName} has offered you to upgrade to audio - internal static let title = Localization.tr("Localizable", "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", "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", "upgrade.video.two_way.title", fallback: "{operatorName} has offered you to upgrade to video") - } - } - } 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.") @@ -543,25 +630,4 @@ internal enum Localization { // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces -// MARK: - Implementation Details - -extension Localization { - private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { - let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) - return String(format: format, locale: Locale.current, arguments: args) - } -} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type - // swiftlint:enable all diff --git a/GliaWidgets/Public/Configuration/Configuration+Mock.swift b/GliaWidgets/Public/Configuration/Configuration+Mock.swift index 95883ef39..409453c4a 100644 --- a/GliaWidgets/Public/Configuration/Configuration+Mock.swift +++ b/GliaWidgets/Public/Configuration/Configuration+Mock.swift @@ -7,12 +7,14 @@ extension Configuration { static func mock( authMethod: AuthorizationMethod = .siteApiKey(id: "site-api-key-id", secret: "site-api-key-secret"), environment: Environment = .beta, - site: String = "site-id" + site: String = "site-id", + companyName: String = "" ) -> Self { Configuration( authorizationMethod: authMethod, environment: environment, - site: site + site: site, + companyName: companyName ) } } diff --git a/GliaWidgets/Public/Configuration/Configuration.swift b/GliaWidgets/Public/Configuration/Configuration.swift index 718719406..84f688404 100644 --- a/GliaWidgets/Public/Configuration/Configuration.swift +++ b/GliaWidgets/Public/Configuration/Configuration.swift @@ -32,7 +32,7 @@ public struct Configuration { visitorContext: VisitorContext? = nil, pushNotifications: PushNotifications = .disabled, isWhiteLabelApp: Bool = false, - companyName: String = L10n.Chat.Connect.Queue.firstText + companyName: String = "" ) { self.authorizationMethod = authorizationMethod self.environment = environment diff --git a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift index 1c7c662c0..dc0106fa9 100644 --- a/GliaWidgets/Public/Glia/Glia+StartEngagement.swift +++ b/GliaWidgets/Public/Glia/Glia+StartEngagement.swift @@ -46,6 +46,16 @@ extension Glia { interactor.queueIds = queueIds } + theme.chat.connect.queue.firstText = companyName( + using: interactor, + themeCompanyName: theme.chat.connect.queue.firstText + ) + + theme.call.connect.queue.firstText = companyName( + using: interactor, + themeCompanyName: theme.call.connect.queue.firstText + ) + let viewFactory = ViewFactory( with: theme, messageRenderer: messageRenderer, @@ -69,6 +79,36 @@ extension Glia { ) } + func companyName( + using interactor: Interactor, + themeCompanyName: String? + ) -> String { + let companyNameStringKey = "general.company_name" + + // Company name has been set on the custom locale and is not empty. + if let remoteCompanyName = stringProviding?.getRemoteString(companyNameStringKey), + !remoteCompanyName.isEmpty { + return remoteCompanyName + } + // As the default value in the theme is not empty, it means that + // the integrator has set a value on the theme itself. Return that + // same value. + else if let themeCompanyName, !themeCompanyName.isEmpty { + return themeCompanyName + } + // 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 + } + // Integrator has not set a company name anywhere, use the default. + else { + // This will return the fallback value every time, because we have + // already determined that the remote string is empty. + return Localization.General.companyNameLocalFallbackOnly + } + } + func startRootCoordinator( with interactor: Interactor, viewFactory: ViewFactory, @@ -124,7 +164,8 @@ extension Glia { startSocketObservation: environment.coreSdk.startSocketObservation, stopSocketObservation: environment.coreSdk.stopSocketObservation, pushNotifications: environment.coreSdk.pushNotifications, - createSendMessagePayload: environment.coreSdk.createSendMessagePayload + createSendMessagePayload: environment.coreSdk.createSendMessagePayload, + orientationManager: environment.orientationManager ) ) rootCoordinator?.delegate = { [weak self] event in diff --git a/GliaWidgets/Public/Glia/Glia.Deprecated.swift b/GliaWidgets/Public/Glia/Glia.Deprecated.swift index b8f1b39de..96e8c7dd4 100644 --- a/GliaWidgets/Public/Glia/Glia.Deprecated.swift +++ b/GliaWidgets/Public/Glia/Glia.Deprecated.swift @@ -114,12 +114,15 @@ extension Glia { interactor = createdInteractor if let callback = completion { - createdInteractor.withConfiguration { [weak createdInteractor] in - guard let interactor = createdInteractor else { return } - interactor.state = GliaCore.sharedInstance - .getCurrentEngagement()?.engagedOperator - .map(InteractorState.engaged) ?? interactor.state - callback() + 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() } } diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index 84f2168bf..538370f33 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -57,6 +57,8 @@ public class Glia { /// Used to monitor engagement state changes. public var onEvent: ((GliaEvent) -> Void)? + var stringProviding: StringProviding? + public lazy var callVisualizer = CallVisualizer( environment: .init( data: environment.data, @@ -81,7 +83,8 @@ public class Glia { uiConfig: { [weak self] in self?.uiConfig }, assetsBuilder: { [weak self] in self?.assetsBuilder ?? .standard }, getCurrentEngagement: environment.coreSdk.getCurrentEngagement, - eventHandler: onEvent + eventHandler: onEvent, + orientationManager: environment.orientationManager ) ) var rootCoordinator: EngagementCoordinator? @@ -124,12 +127,16 @@ public class Glia { interactor = createdInteractor if let callback = completion { - createdInteractor.withConfiguration { [weak createdInteractor] in - guard let interactor = createdInteractor else { return } - interactor.state = GliaCore.sharedInstance - .getCurrentEngagement()?.engagedOperator - .map(InteractorState.engaged) ?? interactor.state - callback() + 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() } } @@ -143,7 +150,7 @@ public class Glia { rootCoordinator?.minimize() } - /// Maximizes engagement view if ongoing engagment exists. + /// Maximizes engagement view if ongoing engagement exists. /// Throws error if ongoing engagement not exist. /// Use this function for resuming engagement view If bubble is hidden programmatically and you need to /// present engagement view. diff --git a/GliaWidgets/Resources/en.lproj/Localizable.strings b/GliaWidgets/Resources/en.lproj/Localizable.strings index bb65d4288..90675244e 100644 --- a/GliaWidgets/Resources/en.lproj/Localizable.strings +++ b/GliaWidgets/Resources/en.lproj/Localizable.strings @@ -1,156 +1,175 @@ "alert.action.settings" = "Settings"; +"android_file_select_picture_title" = "Select picture"; +"android_file_select_file_title" = "Select file"; "android_app_bar_end_engagement_accessibility_label" = "End engagement"; "android_app_bar_nav_up_accessibility" = "Navigate Up"; -"android_bubble_accessibility" = "Back to the Engagement. Floating Button."; -"android_call_mute_accessibility" = "Unmute microphone"; -"android_call_on_hold_accessibility" = "Operator on hold"; -"android_call_speaker_off_accessibility" = "Turn on speaker"; -"android_call_speaker_on_accessibility" = "Turn off speaker"; -"android_call_unmute_accessibility" = "Mute microphone"; -"android_call_video_off_accessibility" = "Turn on video"; -"android_call_video_on_accessibility" = "Turn off video"; +"android_bubble_accessibility" = "Go back to the engagement."; +"android_call_mute_button_accessibility" = "Mute yourself"; +"android_call_on_hold_icon_accessibility" = "Your call is on hold"; +"android_call_turn_speaker_on_button_accessibility" = "Turn on the speaker"; +"android_call_turn_speaker_off_button_accessibility" = "Turn off the speaker"; +"android_call_unmute_button_accessibility" = "Unmute yourself"; +"android_call_turn_video_on_button_accessibility" = "Turn on your camera"; +"android_call_turn_video_off_button_accessibility" = "Turn off your camera"; "android_call_queue_message" = "An operator will be with you shortly."; -"android_chat_audio_accessibility_icon" = "Audio icon"; "android_chat_file_accessibility" = "Attachment %1$s, size %2$s. %3$s"; -"android_chat_file_operator_accessibility" = "Operator\'s attached file %1$s, size %2$s"; -"android_chat_accesandroid_chat_file_visitor_accessibilitysibility_file_visitor" = "Your attached file %1$s, size %2$s"; -"android_chat_file_visitor_delivered_accessibility" = "Your attached file %1$s, size %2$s. \n Delivered"; -"android_chat_accessibility_message" = "Operator\'s message: %1$s"; -"android_chat_operator_accessibility_image" = "Operator\'s attached image"; +"android_chat_operator_file_accessibility" = "Operator\'s attached file %1$s, size %2$s"; +"android_chat_visitor_file_accessibility" = "Your attached file %1$s, size %2$s"; +"android_notification_one_way_video_message" = "The operator’s camera is on. You can now see and hear them."; +"android_notification_one_way_video_no_audio_message" = "The operator’s camera is on. You can now see them."; +"android_notification_two_way_video_message" = "The video exchange is on. You and the operator can see and hear each other."; +"android_notification_two_way_video_no_audio_message" = "The video exchange is on. You and the operator can see each other."; +"android_chat_visitor_file_delivered_accessibility" = "Your attached file %1$s, size %2$s. \n Delivered"; +"android_chat_operator_message_accessibility" = "Operator\'s message: %1$s"; +"android_chat_operator_image_attachment_accessibility" = "Operator\'s attached image"; "android_chat_operator_name_accessibility_message" = "Operator %1$s, Message: %2$s"; -"android_chat_queue_accessibility_label" = "%1$s. \n An MSR will be with you shortly."; -"android_chat_video_accessibility_icon" = "Video icon"; -"android_chat_visitor_accessibility_image" = "Your attached image"; -"android_chat_visitor_delivered_accessibility_image" = "Your attached image. \n Delivered"; -"android_chat_visitor_accessibility_message" = "Your message: %1$s"; -"android_chat_visitor_delivered_accessibility_message" = "Your message: %1$s \n Delivered"; -"android_chat_download_complete" = "The file has been downloaded."; -"android_chat_download_failed" = "Could not download the file. Please try again."; +"android_chat_queue_message_accessibility_label" = "%1$s. \n An MSR will be with you shortly."; +"android_chat_visitor_image_attachment_accessibility" = "Your attached image"; +"android_chat_visitor_image_attachment_delivered_accessibility" = "Your attached image. \n Delivered"; +"android_chat_visitor_message_accessibility" = "Your message: %1$s"; +"android_chat_visitor_message_delivered_accessibility" = "Your message: %1$s \n Delivered"; +"android_chat_download_complete_message" = "The file has been downloaded."; "android_file_not_ready_for_preview" = "The image is not ready for preview. Please try again later."; "android_file_select_title" = "Select Picture"; -"android_file_view_error" = "Can\'t open file"; +"android_file_view_error" = "Could not open the file"; "android_notification_audio_call_channel_name" = "Call notification channel"; "android_notification_audio_call_message" = "Your microphone is now on, and the operator can hear you."; "android_notification_audio_call_title" = "Audio Call Started"; -"android_notification_end_sharing" = "End Sharing"; +"android_notification_end_screen_sharing_title" = "End Sharing"; "android_notification_one_way_video_call_message" = "The operator’s camera is on. You can now see and hear them."; -"android_notification_one_way_video_call_title" = "One-Way Video Started"; +"android_notification_one_way_video_title" = "One-Way Video Started"; "android_notification_one_way_video_no_audio" = "The operator’s camera is on. You can now see them."; "android_notification_screen_sharing_channel_name" = "Screen sharing notification channel"; "android_notification_screen_sharing_message" = "You are currently sharing your screen.\nSelect \'End Sharing\' to stop."; "android_notification_screen_sharing_title" = "Screen Sharing Started"; "android_notification_two_way_video_call_message" = "The video exchange is on. You and the operator can see and hear each other."; -"android_notification_two_way_video_call_title" = "Two-Way Video Started"; +"android_notification_two_way_video_title" = "Two-Way Video Started"; "android_notification_two_way_video_no_audio" = "The video exchange is on. You and the operator can see each other."; -"android_notifications_allow_message" = "We would like to show you notifications in the status bar. Please enable notifications in Settings."; -"android_notifications_allow_title" = "Allow Notifications?"; -"android_overlay_message" = "Allow screen overlay for the proper functioning of the app"; -"android_overlay_title" = "Screen Overlay Permissions Required"; +"android_notification_allow_notifications_message" = "We would like to show you notifications in the status bar. Please enable notifications in Settings."; +"android_notification_allow_notifications_title" = "Allow Notifications?"; +"android_overlay_permission_message" = "Allow screen overlay for the proper functioning of the app"; +"android_overlay_permission_title" = "Screen Overlay Permissions Required"; "android_permissions_message" = "Please make sure all required permissions are enabled."; "android_permissions_title" = "Additional Permissions Required"; -"android_preview_accessibility_image" = "Preview content"; -"android_preview_error_fetch" = "Could not fetch the image. Please try again."; -"android_preview_failed_message" = "Could not load the image preview. Please try again."; +"android_image_preview_accessibility" = "Preview content"; +"android_image_preview_fetch_error" = "Could not fetch the image. Please try again."; +"android_preview_failed" = "Could not load the image preview. Please try again."; "android_preview_menu_save" = "Save"; "android_preview_menu_share" = "Share"; -"android_preview_save_error_message" = "Could not save the image."; -"android_preview_save_success_message" = "The image was saved successfully."; -"android_preview_share_error_message" = "Could not share the image. Please try again."; +"android_preview_save_error" = "Could not save the image."; +"android_preview_save_success" = "The image was saved successfully."; +"android_preview_share_error" = "Could not share the image. Please try again."; "android_preview_share_title" = "Share Image"; "android_preview_title" = "Preview"; -"android_screen_sharing_accessibility_icon" = "Screen sharing icon"; -"android_screen_sharing_offer_with_notifications_message" = "The operator has requested to share your screen. To do this, you need to turn on app\'s notifications. Do this now?"; +"android_screen_sharing_offer_with_notifications_message" = "The operator has asked you to share your screen. To allow this action, please turn on notifications for this app."; "android_screen_sharing_offer_with_notifications_title" = "Start Screen Sharing?"; -"android_survey_error_network" = "The survey could not be submitted due to a network error. Please try again."; -"android_upload_error_engagement_missing" = "Engagement missing"; +"android_survey_error_network" = "The survey could not be submitted due to a network issue. Please try again."; +"android_upload_error_engagement_missing" = "Could not upload the file."; "android_upload_error_file_limit" = "Cannot upload more than 25 files"; "android_upload_error_forbidden" = "File uploading disabled"; "android_upload_error_invalid_input" = "Invalid input"; -"android_upload_error_network" = "Network timed out."; -"android_upload_error_permissions" = "File read access permission denied"; +"android_upload_error_network" = "Connection timed out."; +"android_upload_error_permissions" = "You need permission to perform this action."; "android_upload_menu_take_photo" = "Take photo"; "android_visitor_code_accessibility" = "Your visitor code is %1$s"; "android_visitor_code_loading" = "Loading your visitor code"; -"android_visitor_code_refresh" = "Refresh"; -"call.bubble.accessibility.hint" = "Deactivates minimize."; -"call.bubble.accessibility.label" = "Operator Avatar"; -"call.button.mute" = "Mute"; -"call.button.speaker" = "Speaker"; -"call.button.unmute" = "Unmute"; +"call.bubble.accessibility.hint" = "Expands call view."; +"call.bubble.accessibility.label" = "Go back to the engagement."; +"call.mute.button" = "Mute"; +"call.speaker.button" = "Speaker"; +"call.unmute.button" = "Unmute"; "call.buttons.chat.badge_value.multiple_items.accessibility.label" = "{badgeValue} unread messages"; "call.buttons.chat.badge_value.single_item.accessibility.label" = "{badgeValue} unread message"; "call.connect.first_text.accessibility.hint" = "Displays operator name."; "call.connect.second_text.accessibility.hint" = "Displays call duration."; -"call.header.close.button.accessibility.hint" = "Activates minimize."; -"call.onHold.bottom_text" = "You can continue browsing while you are on hold"; -"call.on_hold" = "On Hold"; -"call.operator.avatar.accessibility.hint" = "Displays operator avatar or placeholder."; -"call.operator.avatar.accessibility.label" = "Operator Avatar"; -"call.operator_name.accessibility.hint" = "Displays operator name."; -"call.video.operator.accessibility.label" = "Operator's Video"; -"call.video.visitor.accessibility.label" = "Your Video"; +"call.header.back.button.accessibility.hint" = "Minimizes call view."; +"call.on_hold.bottom_text" = "You can continue browsing while you are on hold"; +"call.on_hold.icon" = "On Hold"; +"call.operator_avatar.accessibility.hint" = "Shows operator picture."; +"call.operator_avatar.accessibility.label" = "Operator Picture"; +"call.operator_name.accessibility.hint" = "Shows operator name."; +"call.operator_video.accessibility.label" = "Operator's Video"; +"call.visitor_video.accessibility.label" = "Your Video"; "call_visualizer.screen_sharing.message" = "Your Screen is Being Shared"; -"call_visualizer.screen_sharing.title" = "Screen Sharing"; -"call_visualizer.visitor_code.action.close" = "Close"; -"call_visualizer.visitor_code.action.refresh" = "Refresh"; -"call_visualizer.visitor_code.close.accessibility.hint" = "Closes visitor code"; -"call_visualizer.visitor_code.close.accessibility.label" = "Close Button"; -"call_visualizer.visitor_code.refresh.accessibility.hint" = "Generates new visitor code"; +"call_visualizer.screen_sharing.header.title" = "Screen Sharing"; +"call_visualizer.visitor_code.close.accessibility.hint" = "Closes the visitor code"; +"call_visualizer.visitor_code.refresh.accessibility.hint" = "Generates a new visitor code"; "call_visualizer.visitor_code.refresh.accessibility.label" = "Refresh Button"; "call_visualizer.visitor_code.title" = "Your Visitor Code"; -"call_visualizer.visitor_code.title.accessibility.hint" = "Your five-digit visitor code is"; -"chat.duration.accessibility.label" = "Displays call duration."; +"call_visualizer.visitor_code.title.accessibility.hint" = "Shows the five-digit visitor code."; +"call.duration.accessibility.label" = "Call duration."; "chat.attach_files" = "Pick attachment"; -"chat.attachement.photo_library" = "Photo Library"; -"chat.attachement.take_photo" = "Take Photo or Video"; -"chat.attachement.upload.unsupported_file" = "Invalid file type!"; +"chat.attachment.photo_library" = "Photo Library"; +"chat.attachment.take_photo" = "Take Photo or Video"; +"chat.attachment.unsupported_file" = "This file type is not supported."; +"chat.attachment.message.accessibility.label" = "Attachment from {fileSender}"; "chat.download.downloading" = "Downloading file…"; -"chat.file.infected_error" = "Failed to confirm the safety of the file."; -"chat.file.too_large_error" = "File size over 25MB limit!"; -"chat.file.upload.failed" = "Uploading failed"; +"chat.download.failed" = "Could not download the file."; +"chat.file.infected_file.error" = "The safety of the file could not be confirmed."; +"chat.file.size_limit.error" = "File size must be less than 25 MB."; +"chat.file.upload.failed" = "Could not upload the file."; // TODO: - Need to investigate +"chat.file.upload.generic_error" = "Could not upload the file."; // TODO: - Need to investigate "chat.file.upload.in_progress" = "Uploading file…"; -"chat.file.upload.scanning" = "Checking safety of the file…"; +"chat.file.upload.scanning" = "Checking file security…"; "chat.file.upload.success" = "Ready to send"; +"chat.file.upload.network_error" = "Could not upload the file due to a network issue."; "chat.input.placeholder" = "Enter Message"; -"chat.input.send" = "Send"; "chat.message.unread.accessibility.label" = "Unread messages"; "chat.message.start_engagement_placeholder" = "Send a message to start chatting"; -"chat.operator.avatar.accessibility.label" = "Avatar"; -"chat.operator.name.accessibility.label" = "Displays operator name."; -"chat.operator_joined" = "{operatorName} has joined the conversation."; -"chat.status.delivered" = "Delivered"; -"chat.status.typing" = "Operator typing"; +"chat.choice_card.placeholder_message" = "Tap on the answer above"; +"chat.operator_avatar.accessibility.label" = "Operator Picture"; +"chat.operator_name.accessibility.label" = "Operator Name"; +"chat.operator_joined.system_message" = "{operatorName} has joined the conversation."; +"chat.message.delivered" = "Delivered"; +"chat.status.typing" = "Operator is typing"; +"chat.status.typing.accessibility.label" = "{operatorName} is typing"; "chat.unread_message_divider" = "New Messages"; -"chat.upgrade.audio.text" = "Upgraded to Audio Call"; -"chat.upgrade.video.text" = "Upgraded to Video Call"; -"chat.upload.remove.accessibility.label" = "Remove upload"; -"engagement.connect.placeholder" = "We are here to help"; -"engagement.connect.with" = "Connecting with {operatorName}"; -"engagement.default_operator_name" = "Operator"; +"chat.media_upgrade.audio.system_message" = "Upgraded to Audio"; +"chat.media_upgrade.video.system_message" = "Upgraded to Video"; +"chat.file.remove_upload.accessibility.label" = "Remove upload"; +"chat.choice_card.button.disabled.accessibility.label" = "Disabled"; +"chat.choice_card.image.accessibility.label" = "Choice card"; +"engagement.connection_screen.message" = "We are here to help!"; +"engagement.connection_screen.connect_with" = "Connecting with {operatorName}"; +"engagement.default_operator" = "Operator"; "engagement.end.confirmation.header" = "End Engagement?"; -"engagement.end.message" = "Are you sure you want to end engagement?"; +"engagement.end.message" = "Are you sure you want to end this engagement?"; "engagement.ended.header" = "Engagement Ended"; "engagement.ended.message" = "This engagement has ended.\nThank you!"; -"engagement.minimize_video_button" = "Minimize"; -"engagement.offer_upgrade" = "{operatorName} has offered you to upgrade"; -"engagement.queue_closed.header" = "We're sorry"; -"engagement.queue_closed.message" = "Operators are no longer available. \nPlease try again later."; -"engagement.queue_leave.header" = "Are you sure you want to leave?"; -"engagement.queue_leave.message" = "You will lose your place in the queue."; -"engagement.queue_reconnection_failed.try_again" = "Please try again."; -"engagement.queue_transferring.message" = "Transferring"; +"engagement.media_upgrade.audio.info" = "Speak through your device"; +"engagement.media_upgrade.phone.info" = "Enter your number and will call you back."; +"alert.microphone_access.error" = "Unable to access microphone"; +"ios.alert.microphone_access.message" = "Allow access to your microphone in 'Settings' - 'Privacy & Security' - 'Microphone'"; +"alert.camera_access.error" = "Unable to access camera"; +"ios.alert.camera_access.message" = "Allow access to your camera in 'Settings' - 'Privacy & Security' - 'Camera'"; +"alert.media_source_access.error" = "Unable to access media source"; +"ios.alert.media_source.message" = "This media source is not available on your device"; +"alert.screen_sharing.start.header" = "Start Screen Sharing"; +"alert.screen_sharing.start.message" = "{operatorName} has asked you to share your screen."; +"alert.screen_sharing.stop.header" = "Stop Screen Sharing?"; +"alert.screen_sharing.stop.message" = "Are you sure you want to stop sharing your screen?"; +"engagement.minimize_video.button" = "Minimize"; +"engagement.media_upgrade.offer" = "{operatorName} has offered you to upgrade."; +"engagement.queue.closed.header" = "We are sorry! The queue is closed."; +"engagement.queue.closed.message" = "Operators are no longer available. \nPlease try again later."; +"engagement.queue.leave.header" = "Are you sure you want to leave?"; +"engagement.queue.leave.message" = "You will lose your place in the queue."; +"engagement.queue.reconnection.failed" = "Please try again later."; +"engagement.queue.transferring" = "Transferring"; +"ios.engagement.connection_screen.video_notice" = "(By default, your video will be turned off)"; "engagement.queue_wait.message" = "You can continue browsing and we will connect you automatically."; "engagement.secure_messaging.title" = "Messaging"; -"error.general" = "Something went wrong"; -"error.internal" = "Internal error"; -"error.unexpected.title" = "We're sorry, there has been an unexpected error."; +"error.general" = "Something went wrong."; +"error.internal" = "Something went wrong."; +"error.unexpected" = "Something went wrong."; "general.accept" = "Accept"; "general.back" = "Back"; "general.browse" = "Browse"; "general.cancel" = "Cancel"; "general.close" = "Close"; +"general.close.accessibility" = "Close Button"; "general.comment" = "Comment"; -"general.company_name" = "CompanyName"; +"general.company_name" = "Company Name"; "general.decline" = "Decline"; "general.download" = "Download"; "general.end" = "End"; @@ -158,20 +177,22 @@ "general.no" = "No"; "general.ok" = "Ok"; "general.open" = "Open"; -"general.powered_by" = "Powered by Glia"; +"general.powered" = "Powered by"; "general.retry" = "Retry"; "general.selected" = "Selected"; "general.submit" = "Submit"; "general.thank_you" = "Thank you!"; "general.yes" = "Yes"; "general.you" = "You"; -"gva.error_unsupported" = "This action is not currently supported on mobile."; -"media.audio.name" = "Audio"; -"media.messaging.description" = "Send a message and we’ll get back to you \nwithin 48 hours"; -"media.phone.name" = "Phone"; -"media.text.name" = "Chat"; -"media.video.name" = "Video"; -"message_center.check_messages" = "Check messages"; +"general.refresh" = "Refresh"; +"general.send" = "Send"; +"general.sending" = "Sending…"; +"gva.unsupported_action.error" = "This action is not currently supported on mobile."; +"engagement.audio.title" = "Audio"; +"engagement.phone.title" = "Phone"; +"engagement.chat.title" = "Chat"; +"engagement.video.title" = "Video"; +"message_center.welcome.check_messages" = "Check messages"; "message_center.confirmation.check_messages.accessibility.hint" = "Navigates you to the chat transcript."; "message_center.confirmation.check_messages.accessibility.label" = "Check messages"; "message_center.confirmation.subtitle" = "Your message has been sent. We will get back to you within 48 hours."; @@ -179,26 +200,26 @@ "message_center.not_authenticated.message" = "We could not verify your authentication status."; "message_center.unavailable.message" = "The Message Center is currently unavailable. Please try again later."; "message_center.unavailable.title" = "Message Center Unavailable"; +"message_center.welcome.subtitle" = "Send a message and we will get back to you within 48 hours."; "message_center.welcome.check_messages.accessibility.hint" = "Navigates you to the chat transcript."; "message_center.welcome.file_picker.accessibility.hint" = "Opens the file picker to attach media."; -"message_center.welcome.file_picker_label.accessibility.label" = "File picker"; -"message_center.welcome.message_length_warning" = "The message cannot exceed 10000 characters."; -"message_center.welcome.message_text_view.placeholder" = "Enter your message"; +"message_center.welcome.file_picker.accessibility.label" = "File picker"; +"message_center.welcome.message_length.error" = "The message cannot exceed 10,000 characters."; +"message_center.welcome.message_input.placeholder" = "Enter your message"; "message_center.welcome.message_title" = "Your message"; "message_center.welcome.send.accessibility.hint" = "Sends a secure message."; "message_center.welcome.title" = "Welcome to Message Center"; "screen_sharing.visitor_screen.disclaimer.title" = "You are about to share your screen"; -"screen_sharing.visitor_screen.end" = "End screen sharing"; -"screensharing.visitor_screen.disclaimer.info" = "Depending on your selection, your entire screen might be shared with the operator, not just the application window."; -"send_message.send" = "Send message"; -"send_message.sending" = "Sending…"; +"screen_sharing.visitor_screen.end.title" = "End Screen Sharing"; +"screen_sharing.visitor_screen.end.accessibility.hint" = "Ends screen sharing"; +"screen_sharing.visitor_screen.disclaimer.info" = "Depending on your selection, your entire screen might be shared with the operator, not just the application window."; "survey.action.validation_error" = "Please provide an answer."; "survey.question.option_button.selected.accessibility.label" = "Selected: {buttonTitle}"; "survey.question.option_button.unselected.accessibility.label" = "Unselected: {buttonTitle}"; -"survey.question.text_field.accessibility.hint" = "Enter the answer"; -"survey.question.title.accessibility.label" = "Required"; -"survey.validation.title.accessibility.label" = "Please provide an answer for question above"; -"upgrade.audio.title" = "{operatorName} has offered you to upgrade to audio"; -"upgrade.video.one_way.title" = "{operatorName} has offered you to see their video"; -"upgrade.video.two_way.title" = "{operatorName} has offered you to upgrade to video"; +"survey.question.input.accessibility.hint" = "Enter the answer"; +"survey.question.required.accessibility.label" = "This is a required question."; +"survey.validation.title.accessibility.label" = "Please provide an answer for the question above."; +"media_upgrade.audio.title" = "{operatorName} has offered you to upgrade to audio"; +"media_upgrade.video.one_way.title" = "{operatorName} has offered you to see their video"; +"media_upgrade.video.two_way.title" = "{operatorName} has offered you to upgrade to video"; "visitor_code.failed" = "Could not load the visitor code. Please try refreshing."; diff --git a/GliaWidgets/SecureConversations/ChatTranscript/SecureConversations.TranscriptModel.swift b/GliaWidgets/SecureConversations/ChatTranscript/SecureConversations.TranscriptModel.swift index cae493e8e..4f4970037 100644 --- a/GliaWidgets/SecureConversations/ChatTranscript/SecureConversations.TranscriptModel.swift +++ b/GliaWidgets/SecureConversations/ChatTranscript/SecureConversations.TranscriptModel.swift @@ -59,7 +59,11 @@ extension SecureConversations { var availability: Availability var interactor: Interactor - private (set) var isSecureConversationsAvailable: Bool = true + private (set) var isSecureConversationsAvailable: Bool = true { + didSet { + action?(.transcript(.messageCenterAvailabilityUpdated)) + } + } var siteConfiguration: CoreSdkClient.Site? @@ -175,6 +179,7 @@ extension SecureConversations { let configuration = self.environment.alertConfiguration.unavailableMessageCenter self.reportMessageCenterUnavailable(configuration: configuration) case .success(.unavailable(.unauthenticated)): + self.isSecureConversationsAvailable = false let configuration = self.environment.alertConfiguration.unavailableMessageCenterForBeingUnauthenticated self.reportMessageCenterUnavailable(configuration: configuration) } @@ -746,3 +751,12 @@ extension SecureConversations.TranscriptModel { } } + +#if DEBUG +extension SecureConversations.TranscriptModel { + /// Setter for `isSecureConversationsAvailable`. Used in unit tests. + func setIsSecureConversationsAvailable(_ available: Bool) { + self.isSecureConversationsAvailable = available + } +} +#endif diff --git a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationView.swift b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationView.swift index 7aca96ded..d15265e26 100644 --- a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationView.swift +++ b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationView.swift @@ -1,216 +1,87 @@ -import Foundation -import UIKit +import SwiftUI extension SecureConversations { - final class ConfirmationView: BaseView { - static let sideMargin = 24.0 - static let portraitConfirmationImageSize = 144.0 - static let landscapeConfirmationImageSize = 90.0 - - struct Props: Equatable { - let style: ConfirmationStyle - let header: Header.Props - let checkMessageButtonTap: Cmd - } - - private let header: Header - - var topRootStackViewConstraint: NSLayoutConstraint? - var confirmationImageViewWidthConstraints: NSLayoutConstraint? - var confirmationImageViewHeightConstraints: NSLayoutConstraint? - - lazy var rootStackView = UIStackView.make( - .vertical, - spacing: 0, - distribution: .fill, - alignment: .center - )( - confirmationImageView, - titleLabel, - subtitleLabel, - spacer, - checkMessagesButton - ) - - let confirmationImageView = UIImageView().makeView { imageView in - imageView.image = Asset.mcConfirmation.image.withRenderingMode(.alwaysTemplate) - } - let titleLabel = UILabel().makeView { label in - label.numberOfLines = 0 - label.textAlignment = .center - } - - let subtitleLabel = UILabel().makeView { label in - label.numberOfLines = 0 - label.textAlignment = .center - } - - // Flexible space to accommodate the check messages button - // at the bottom of the view. - let spacer = UIView() - - lazy var checkMessagesButton = UIButton(type: .custom).makeView { button in - button.addTarget( - self, - action: #selector(handleCheckMessagesButtonTap), - for: .touchUpInside - ) - - button.layer.cornerRadius = 4 - } - - var props: Props { - didSet { - renderProps() + struct ConfirmationViewSwiftUI: View { + @ObservedObject var model: Model + + var body: some View { + ZStack { + backgroundColor() + VStack(spacing: 0) { + HeaderSwiftUI(model: model.makeHeaderModel()) + VStack(spacing: 0) { + Spacer(minLength: 1) + checkmarkImage() + titleView() + subtitleView() + Spacer(minLength: 1) + confirmationButtonView() + } + .padding(.bottom, model.orientation.isPortrait ? 24 : 8) + .padding(.horizontal, 24) + } + .edgesIgnoringSafeArea(.top) } } + } +} - init(props: Props) { - self.header = Header( - props: props.header - ) - self.props = props - super.init() - } - - @available(*, unavailable) - required init() { - fatalError("init() has not been implemented") - } - - override func defineLayout() { - super.defineLayout() - defineHeaderLayout() - defineRootStackViewLayout() - defineConfirmationImageViewLayout() - defineTitleLabelLayout() - defineSubtitleLabelLayout() - defineSpacerLayout() - defineCheckMessagesButtonLayout() - renderProps() - } - - override func setup() { - super.setup() - addSubview(rootStackView) - } - - override func layoutSubviews() { - super.layoutSubviews() - - changeConfirmationImageViewDimensions() - } - - private func changeConfirmationImageViewDimensions() { - // The portrait factor is the factor between the space from the header - // to the beginning of the stack view versus the height of the screen - // in the Figma design. The landscape factor was calculated through trial - // and error to avoid a bug where the image was so big that it would hide - // the text below it. - let factor = currentOrientation.isPortrait ? 0.2783 : 0.075 - topRootStackViewConstraint?.constant = self.rootStackView.frame.height * factor - - let imageSize = currentOrientation.isPortrait ? - Self.portraitConfirmationImageSize : - Self.landscapeConfirmationImageSize - - confirmationImageViewWidthConstraints?.constant = imageSize - confirmationImageViewHeightConstraints?.constant = imageSize - } - - private func defineHeaderLayout() { - addSubview(header) - var constraints = [NSLayoutConstraint](); defer { constraints.activate() } - constraints += header.layoutInSuperview(edges: .horizontal) - constraints += header.layoutInSuperview(edges: .top) - } - - func defineRootStackViewLayout() { - topRootStackViewConstraint = rootStackView.topAnchor.constraint(equalTo: header.bottomAnchor) - NSLayoutConstraint.activate([ - rootStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Self.sideMargin), - rootStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Self.sideMargin), - topRootStackViewConstraint, - rootStackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -Self.sideMargin) - ].compactMap { $0 }) - } - - private func defineConfirmationImageViewLayout() { - confirmationImageViewWidthConstraints = confirmationImageView.widthAnchor.constraint( - equalToConstant: Self.portraitConfirmationImageSize - ) - confirmationImageViewHeightConstraints = confirmationImageView.heightAnchor.constraint( - equalToConstant: Self.portraitConfirmationImageSize - ) - - NSLayoutConstraint.activate([ - confirmationImageView.topAnchor.constraint(equalTo: rootStackView.topAnchor), - confirmationImageViewWidthConstraints, - confirmationImageViewHeightConstraints - ].compactMap { $0 }) - - rootStackView.setCustomSpacing(32, after: confirmationImageView) - } - - private func defineTitleLabelLayout() { - rootStackView.setCustomSpacing(16, after: titleLabel) - } - - private func defineSubtitleLabelLayout() { - rootStackView.setCustomSpacing(0, after: subtitleLabel) - } - - private func defineSpacerLayout() { - NSLayoutConstraint.activate([ - spacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 1) - ]) - } - private func defineCheckMessagesButtonLayout() { - NSLayoutConstraint.activate([ - checkMessagesButton.widthAnchor.constraint( - equalTo: rootStackView.widthAnchor - ), - checkMessagesButton.heightAnchor.constraint(equalToConstant: 48) - ]) - } - @objc func handleCheckMessagesButtonTap() { - props.checkMessageButtonTap() - } - - private func renderProps() { - header.props = props.header - header.showCloseButton() - - confirmationImageView.tintColor = props.style.confirmationImageTint - titleLabel.text = props.style.titleStyle.text - titleLabel.textColor = props.style.titleStyle.color - titleLabel.font = props.style.titleStyle.font - setFontScalingEnabled( - props.style.titleStyle.accessibility.isFontScalingEnabled, - for: titleLabel - ) +private extension SecureConversations.ConfirmationViewSwiftUI { + @ViewBuilder + func backgroundColor() -> some View { + SwiftUI.Color(model.style.backgroundColor) + .edgesIgnoringSafeArea(.all) + } + @ViewBuilder + func checkmarkImage() -> some View { + SwiftUI.Image(uiImage: Asset.mcConfirmation.image.withRenderingMode(.alwaysTemplate)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: model.orientation.isPortrait ? 100.0 : 70.0) + .foregroundColor(SwiftUI.Color(model.style.confirmationImageTint)) + .padding(.bottom, model.orientation.isPortrait ? 32 : 8) + .migrationAccessibilityHidden(true) + } - subtitleLabel.text = props.style.subtitleStyle.text - subtitleLabel.textColor = props.style.subtitleStyle.color - subtitleLabel.font = props.style.subtitleStyle.font - setFontScalingEnabled( - props.style.subtitleStyle.accessibility.isFontScalingEnabled, - for: subtitleLabel - ) + @ViewBuilder + func titleView() -> some View { + Text(model.style.titleStyle.text) + .font(.convert(model.style.titleStyle.font)) + .multilineTextAlignment(.center) + .foregroundColor(SwiftUI.Color(model.style.titleStyle.color)) + .padding(.bottom, model.orientation.isPortrait ? 16 : 8) + } - checkMessagesButton.setTitle(props.style.checkMessagesButtonStyle.title, for: .normal) - checkMessagesButton.setTitleColor(props.style.checkMessagesButtonStyle.textColor, for: .normal) - checkMessagesButton.backgroundColor = props.style.checkMessagesButtonStyle.backgroundColor - checkMessagesButton.accessibilityTraits = .button - checkMessagesButton.accessibilityIdentifier = "secureConversations_confirmationCheckMessages_button" - checkMessagesButton.accessibilityLabel = props.style.checkMessagesButtonStyle.accessibility.label - checkMessagesButton.accessibilityHint = props.style.checkMessagesButtonStyle.accessibility.hint - setFontScalingEnabled( - props.style.checkMessagesButtonStyle.accessibility.isFontScalingEnabled, - for: checkMessagesButton - ) + @ViewBuilder + func subtitleView() -> some View { + Text(model.style.subtitleStyle.text) + .font(.convert(model.style.subtitleStyle.font)) + .multilineTextAlignment(.center) + .lineLimit(nil) + .foregroundColor(SwiftUI.Color(model.style.subtitleStyle.color)) + } - backgroundColor = props.style.backgroundColor - } + @ViewBuilder + func confirmationButtonView() -> some View { + SwiftUI.Button { + model.event(.chatTranscriptScreenRequested) + } label: { + Text(model.style.checkMessagesButtonStyle.title) + .font(.convert(model.style.checkMessagesButtonStyle.font)) + .multilineTextAlignment(.center) + .foregroundColor(SwiftUI.Color(model.style.checkMessagesButtonStyle.textColor)) + .padding(.vertical, 4) + .padding(.horizontal, 16) + .frame( + maxWidth: .infinity, + minHeight: 48, + idealHeight: 48 + ) + .background(SwiftUI.Color(model.style.checkMessagesButtonStyle.backgroundColor)) + .cornerRadius(4) + } + .migrationAccessibilityIdentifier("secureConversations_confirmationCheckMessages_button") + .migrationAccessibilityLabel(model.style.checkMessagesButtonStyle.accessibility.label) + .migrationAccessibilityHint(model.style.checkMessagesButtonStyle.accessibility.hint) } } diff --git a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewController.swift b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewController.swift index c1904fca1..c7f2a64ab 100644 --- a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewController.swift +++ b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewController.swift @@ -1,25 +1,12 @@ import UIKit +import SwiftUI extension SecureConversations { final class ConfirmationViewController: UIViewController { - var props: Props { - didSet { - guard props != oldValue else { return } - renderProps() - } - } - - private let viewFactory: ViewFactory - private let viewModel: ConfirmationViewModel + private let model: ConfirmationViewSwiftUI.Model - init( - viewModel: ConfirmationViewModel, - viewFactory: ViewFactory, - props: Props - ) { - self.viewModel = viewModel - self.viewFactory = viewFactory - self.props = props + init(model: ConfirmationViewSwiftUI.Model) { + self.model = model super.init(nibName: nil, bundle: nil) } @@ -30,26 +17,21 @@ extension SecureConversations { override func loadView() { super.loadView() - renderProps() - } + let hostingController: UIHostingController + let confirmationView = ConfirmationViewSwiftUI(model: model) - func renderProps() { - let confirmationView: ConfirmationView - if let currentView = view as? ConfirmationView { - confirmationView = currentView - } else { - confirmationView = viewFactory.makeSecureConversationsConfirmationView( - props: props.confirmationViewProps - ) - view = confirmationView - } - confirmationView.props = props.confirmationViewProps + hostingController = UIHostingController(rootView: confirmationView) + hostingController.willMove(toParent: self) + addChild(hostingController) + view.addSubview(hostingController.view) + hostingController.didMove(toParent: self) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ]) } } } - -extension SecureConversations.ConfirmationViewController { - struct Props: Equatable { - let confirmationViewProps: SecureConversations.ConfirmationView.Props - } -} diff --git a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.swift b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.swift index f21b60899..faadb8390 100644 --- a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.swift +++ b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.swift @@ -1,87 +1,101 @@ import Foundation +import UIKit +import Combine -extension SecureConversations { - final class ConfirmationViewModel: ViewModel { - var action: ((Action) -> Void)? +extension SecureConversations.ConfirmationViewSwiftUI { + final class Model: ObservableObject { + @Published private(set) var orientation: UIInterfaceOrientation + let environment: Environment + let style: SecureConversations.ConfirmationStyle var delegate: ((DelegateEvent) -> Void)? - var environment: Environment + let orientationManager: OrientationManager + var cancellables: Set = [] - init(environment: Environment) { + init( + environment: Environment, + style: SecureConversations.ConfirmationStyle, + delegate: ((DelegateEvent) -> Void)? + ) { self.environment = environment + self.style = style + self.delegate = delegate + self.orientationManager = environment.orientationManager + self.orientation = orientationManager.orientation + orientationManager.$orientation.sink { [weak self] orientation in + self?.orientation = orientation + }.store(in: &self.cancellables) } + } +} - func event(_ event: Event) { - switch event { - case .closeTapped: - delegate?(.closeTapped) - } - } - - func reportChange() { - delegate?(.renderProps(props())) +// MARK: - Public Methods +extension SecureConversations.ConfirmationViewSwiftUI.Model { + func event(_ event: Event) { + switch event { + case .closeTapped: + delegate?(.closeTapped) + case .chatTranscriptScreenRequested: + delegate?(.chatTranscriptScreenRequested) } } } -extension SecureConversations.ConfirmationViewModel { - func props() -> SecureConversations.ConfirmationViewController.Props { - let confirmationStyle = environment.confirmationStyle - let confirmationViewProps = SecureConversations.ConfirmationView.Props( - style: confirmationStyle, - header: Self.buildHeaderProps( - style: confirmationStyle, - closeButtonCmd: Cmd(closure: { [weak self] in self?.delegate?(.closeTapped) }) - ), - checkMessageButtonTap: Cmd { [weak self] in self?.delegate?(.chatTranscriptScreenRequested) } +// MARK: - Private Methods +extension SecureConversations.ConfirmationViewSwiftUI.Model { + func makeHeaderModel() -> HeaderSwiftUI.Model { + let endButtonProps: ActionButtonSwiftUI.Model = .init( + style: style.header.endButton, + accessibilityIdentifier: "header_end_button", + isEnabled: false, + isHidden: true ) - let viewControllerProps = SecureConversations.ConfirmationViewController.Props( - confirmationViewProps: confirmationViewProps + let closeButtonProps: HeaderButtonSwiftUI.Model = .init( + tap: Cmd(closure: { [weak self] in + self?.delegate?(.closeTapped) + }), + style: style.header.closeButton, + accessibilityIdentifier: "header_close_button", + isEnabled: true, + isHidden: false ) - return viewControllerProps - } + let endScreenShareButtonProps: HeaderButtonSwiftUI.Model = .init( + style: style.header.endScreenShareButton, + accessibilityIdentifier: "header_end_screen_sharing_button", + isEnabled: false, + isHidden: true + ) - static func buildHeaderProps( - style: SecureConversations.ConfirmationStyle, - closeButtonCmd: Cmd - ) -> Header.Props { - let backButton = style.header.backButton.map { HeaderButton.Props(style: $0) } + let environment: HeaderSwiftUI.Environment = .init(uiApplication: environment.uiApplication) - return Header.Props( + return .init( title: style.headerTitle, effect: .none, - endButton: .init(style: style.header.endButton, accessibilityIdentifier: "header_end_button"), - backButton: backButton, - closeButton: .init(tap: closeButtonCmd, style: style.header.closeButton), - endScreenshareButton: .init(style: style.header.endScreenShareButton), - style: style.header + endButton: endButtonProps, + backButton: nil, + closeButton: closeButtonProps, + endScreenshareButton: endScreenShareButtonProps, + style: style.header, + environment: environment ) } } -extension SecureConversations.ConfirmationViewModel { +// MARK: - Objects +extension SecureConversations.ConfirmationViewSwiftUI.Model { enum Event { case closeTapped - } - - enum Action { - case start + case chatTranscriptScreenRequested } enum DelegateEvent { case closeTapped - case renderProps(SecureConversations.ConfirmationViewController.Props) case chatTranscriptScreenRequested } - enum StartAction { - case none - } -} - -extension SecureConversations.ConfirmationViewModel { struct Environment { - var confirmationStyle: SecureConversations.ConfirmationStyle + var orientationManager: OrientationManager + var uiApplication: UIKitBased.UIApplication } } diff --git a/GliaWidgets/SecureConversations/Confirmation/Theme+SecureConversationsConfirmation.swift b/GliaWidgets/SecureConversations/Confirmation/Theme+SecureConversationsConfirmation.swift index 942ae8ef7..48b120483 100644 --- a/GliaWidgets/SecureConversations/Confirmation/Theme+SecureConversationsConfirmation.swift +++ b/GliaWidgets/SecureConversations/Confirmation/Theme+SecureConversationsConfirmation.swift @@ -4,41 +4,39 @@ extension Theme { /// Default style for confirmation screen in secure conversation. Be aware it depends on /// `self.colors` (ThemeColor). var defaultSecureConversationsConfirmationStyle: SecureConversations.ConfirmationStyle { - typealias Confirmation = L10n.MessageCenter.Confirmation - let chatStyle = chatStyle var header = chatStyle.header header.backButton = nil let titleStyle = SecureConversations.ConfirmationStyle.TitleStyle( - text: Confirmation.title, - font: font.header3, + text: Localization.General.thankYou, + font: font.header1, color: color.baseDark, accessibility: .init(isFontScalingEnabled: true) ) let subtitleStyle = SecureConversations.ConfirmationStyle.SubtitleStyle( - text: Confirmation.subtitle, + text: Localization.MessageCenter.Confirmation.subtitle, font: font.bodyText, color: color.baseDark, accessibility: .init(isFontScalingEnabled: true) ) let checkMessagesButtonStyle = SecureConversations.ConfirmationStyle.CheckMessagesButtonStyle( - title: Confirmation.checkMessages, + title: Localization.MessageCenter.Welcome.checkMessages, font: font.bodyText, textColor: color.baseLight, backgroundColor: color.primary, accessibility: .init( isFontScalingEnabled: true, - label: Confirmation.Accessibility.checkMessagesLabel, - hint: Confirmation.Accessibility.checkMessagesHint + label: Localization.MessageCenter.Confirmation.CheckMessages.Accessibility.label, + hint: Localization.MessageCenter.Confirmation.CheckMessages.Accessibility.hint ) ) return .init( header: header, - headerTitle: Confirmation.header, + headerTitle: Localization.MessageCenter.header, confirmationImage: Asset.mcEnvelope.image, confirmationImageTint: color.primary, titleStyle: titleStyle, diff --git a/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift b/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift index 9eac3423e..728c494c9 100644 --- a/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift +++ b/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift @@ -160,29 +160,24 @@ extension SecureConversations { } func presentSecureConversationsConfirmationViewController() { - let viewModel = SecureConversations.ConfirmationViewModel( - environment: .init( - confirmationStyle: viewFactory.theme.secureConversationsConfirmation - ) + let environment: ConfirmationViewSwiftUI.Model.Environment = .init( + orientationManager: environment.orientationManager, + uiApplication: environment.uiApplication ) - let controller = SecureConversations.ConfirmationViewController( - viewModel: viewModel, - viewFactory: viewFactory, - props: viewModel.props() - ) + let model = SecureConversations.ConfirmationViewSwiftUI.Model( + environment: environment, + style: viewFactory.theme.secureConversationsConfirmation, + delegate: { [weak self] event in + switch event { + case .closeTapped: + self?.delegate?(.closeTapped(.doNotPresentSurvey)) + case .chatTranscriptScreenRequested: + self?.navigateToTranscript() + } + }) - viewModel.delegate = { [weak self, weak controller] event in - switch event { - case .closeTapped: - self?.delegate?(.closeTapped(.doNotPresentSurvey)) - // Bind changes in view model to view controller. - case let .renderProps(props): - controller?.props = props - case .chatTranscriptScreenRequested: - self?.navigateToTranscript() - } - } + let controller = SecureConversations.ConfirmationViewController(model: model) self.navigationPresenter.push( controller, @@ -326,6 +321,7 @@ 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 @@ -351,6 +347,7 @@ extension SecureConversations.Coordinator { var startSocketObservation: CoreSdkClient.StartSocketObservation var stopSocketObservation: CoreSdkClient.StopSocketObservation var createSendMessagePayload: CoreSdkClient.CreateSendMessagePayload + var orientationManager: OrientationManager } enum DelegateEvent { diff --git a/GliaWidgets/SecureConversations/Welcome/Theme+SecureConversationsWelcome.swift b/GliaWidgets/SecureConversations/Welcome/Theme+SecureConversationsWelcome.swift index 6b25526b0..e418b6be1 100644 --- a/GliaWidgets/SecureConversations/Welcome/Theme+SecureConversationsWelcome.swift +++ b/GliaWidgets/SecureConversations/Welcome/Theme+SecureConversationsWelcome.swift @@ -2,14 +2,14 @@ import UIKit extension Theme { var secureConversationsWelcomeStyle: SecureConversations.WelcomeStyle { - typealias Welcome = L10n.MessageCenter.Welcome + typealias Welcome = Localization.MessageCenter.Welcome let chat = chatStyle var header = chat.header header.backButton = nil let welcomeTitleStyle = SecureConversations.WelcomeStyle.TitleStyle( - text: Welcome.title, + text: Localization.MessageCenter.Welcome.title, font: font.header3, textStyle: .title3, color: .black, @@ -17,7 +17,7 @@ extension Theme { ) let welcomeSubtitleStyle = SecureConversations.WelcomeStyle.SubtitleStyle( - text: Welcome.subtitle, + text: Localization.MessageCenter.Welcome.subtitle, font: font.subtitle, textStyle: .footnote, color: .black, @@ -25,19 +25,19 @@ extension Theme { ) let checkMessagesButtonStyle = SecureConversations.WelcomeStyle.CheckMessagesButtonStyle( - title: Welcome.checkMessages, + title: Localization.MessageCenter.Welcome.checkMessages, font: font.header2, textStyle: .title2, color: color.primary, accessibility: .init( isFontScalingEnabled: true, - label: Welcome.Accessibility.checkMessagesLabel, - hint: Welcome.Accessibility.checkMessagesHint + label: Localization.MessageCenter.Welcome.checkMessages, + hint: Localization.MessageCenter.Welcome.CheckMessages.Accessibility.hint ) ) let messageTitleStyle = SecureConversations.WelcomeStyle.MessageTitleStyle( - title: Welcome.messageTitle, + title: Localization.MessageCenter.Welcome.messageTitle, font: font.mediumSubtitle1, textStyle: .subheadline, color: .black, @@ -45,7 +45,7 @@ extension Theme { ) let messageTextViewNormalStyle = SecureConversations.WelcomeStyle.MessageTextViewNormalStyle( - placeholderText: Welcome.messageTextViewNormal, + placeholderText: Localization.MessageCenter.Welcome.MessageInput.placeholder, placeholderFont: font.bodyText, placeholderColor: color.baseNormal, textFont: font.bodyText, @@ -54,12 +54,12 @@ extension Theme { borderColor: color.baseNormal, borderWidth: 1, cornerRadius: 4, - backgroundColor: color.background, + backgroundColor: color.baseLight, accessibility: .init(isFontScalingEnabled: true) ) let messageTextViewActiveStyle = SecureConversations.WelcomeStyle.MessageTextViewActiveStyle( - placeholderText: Welcome.messageTextViewActive, + placeholderText: Localization.MessageCenter.Welcome.MessageInput.placeholder, placeholderFont: font.bodyText, placeholderColor: color.baseNormal, textFont: font.bodyText, @@ -68,12 +68,12 @@ extension Theme { borderColor: color.primary, borderWidth: 1, cornerRadius: 4, - backgroundColor: color.background, + backgroundColor: color.baseLight, accessibility: .init(isFontScalingEnabled: true) ) let messageTextViewDisabledStyle = SecureConversations.WelcomeStyle.MessageTextViewDisabledStyle( - placeholderText: Welcome.messageTextViewDisabled, + placeholderText: Localization.MessageCenter.Welcome.MessageInput.placeholder, placeholderFont: font.bodyText, placeholderColor: color.baseNormal, textFont: font.bodyText, @@ -93,7 +93,7 @@ extension Theme { ) let sendButtonEnabledStyle = SecureConversations.WelcomeStyle.SendButtonEnabledStyle( - title: Welcome.sendEnabled, + title: Localization.General.send, font: font.bodyText, textStyle: .body, textColor: color.baseLight, @@ -103,13 +103,13 @@ extension Theme { cornerRadius: 4, accessibility: .init( isFontScalingEnabled: true, - label: Welcome.Accessibility.sendLabel, - hint: Welcome.Accessibility.sendHint + label: Localization.General.send, + hint: Localization.MessageCenter.Welcome.Send.Accessibility.hint ) ) let sendButtonDisabledStyle = SecureConversations.WelcomeStyle.SendButtonDisabledStyle( - title: Welcome.sendDisabled, + title: Localization.General.send, font: font.bodyText, textStyle: .body, textColor: .disabledTitle, @@ -119,13 +119,13 @@ extension Theme { cornerRadius: 4, accessibility: .init( isFontScalingEnabled: true, - label: Welcome.Accessibility.sendLabel, - hint: Welcome.Accessibility.sendHint + label: Localization.General.send, + hint: Localization.MessageCenter.Welcome.Send.Accessibility.hint ) ) let sendButtonLoadingStyle = SecureConversations.WelcomeStyle.SendButtonLoadingStyle( - title: Welcome.sendLoading, + title: Localization.General.send, font: font.bodyText, textStyle: .body, textColor: .disabledTitle, @@ -136,8 +136,8 @@ extension Theme { cornerRadius: 4, accessibility: .init( isFontScalingEnabled: true, - label: Welcome.Accessibility.sendLabel, - hint: Welcome.Accessibility.sendHint + label: Localization.General.send, + hint: Localization.MessageCenter.Welcome.Send.Accessibility.hint ) ) @@ -152,7 +152,7 @@ extension Theme { textFont: .systemFont(ofSize: 12.0), textStyle: .caption1, iconColor: color.systemNegative, - messageLengthLimitText: L10n.MessageCenter.Welcome.messageLengthWarning, + messageLengthLimitText: Localization.MessageCenter.Welcome.MessageLength.error, accessibility: .init(isFontScalingEnabled: true) ) @@ -161,8 +161,8 @@ extension Theme { disabledColor: .lightGray, accessibility: .init( isFontScalingEnabled: true, - accessibilityLabel: Welcome.Accessibility.filePickerLabel, - accessibilityHint: Welcome.Accessibility.filePickerHint + accessibilityLabel: Localization.MessageCenter.Welcome.FilePicker.Accessibility.label, + accessibilityHint: Localization.MessageCenter.Welcome.FilePicker.Accessibility.hint ) ) @@ -172,45 +172,40 @@ extension Theme { ) var uploadListStyle: MessageCenterFileUploadListStyle { - // TODO: Introduce dedicated localization for Secure conversations upload list instaed of using Chat's one. - // MOB-1831 - typealias Upload = L10n.Chat.Upload - typealias Accessibility = L10n.Chat.Accessibility.Upload - let filePreview = FilePreviewStyle( fileFont: font.subtitle, fileColor: color.baseLight, errorIcon: Asset.uploadError.image, errorIconColor: color.systemNegative, backgroundColor: color.primary, - errorBackgroundColor: Color.lightGrey, + errorBackgroundColor: color.baseNeutral, accessibility: .init(isFontScalingEnabled: true) ) let uploading = FileUploadStateStyle( - text: Upload.uploading, + text: Localization.Chat.File.Upload.inProgress, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.baseNormal ) let uploaded = FileUploadStateStyle( - text: Upload.uploaded, + text: Localization.Chat.File.Upload.success, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.baseNormal ) let error = FileUploadErrorStateStyle( - text: Upload.failed, + text: Localization.Chat.File.Upload.failed, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.systemNegative, - infoFileTooBig: Upload.Error.fileTooBig, - infoUnsupportedFileType: Upload.Error.unsupportedFileType, - infoSafetyCheckFailed: Upload.Error.safetyCheckFailed, - infoNetworkError: Upload.Error.network, - infoGenericError: Upload.Error.generic + infoFileTooBig: Localization.Chat.File.SizeLimit.error, + infoUnsupportedFileType: Localization.Chat.Attachment.unsupportedFile, + infoSafetyCheckFailed: Localization.Chat.File.InfectedFile.error, + infoNetworkError: Localization.Chat.File.Upload.networkError, + infoGenericError: Localization.Chat.File.Upload.genericError ) let upload = MessageCenterFileUploadStyle( filePreview: filePreview, @@ -219,14 +214,14 @@ extension Theme { error: error, progressColor: color.primary, errorProgressColor: color.systemNegative, - progressBackgroundColor: Color.lightGrey, + progressBackgroundColor: color.baseNeutral, removeButtonImage: Asset.mcRemoveUpload.image, removeButtonColor: color.baseNormal, backgroundColor: .commonGray, accessibility: .init( - removeButtonAccessibilityLabel: Accessibility.RemoveUpload.label, - progressPercentValue: Accessibility.Progress.percentValue, - fileNameWithProgressValue: Accessibility.Progress.fileNameWithProgressValue, + removeButtonAccessibilityLabel: Localization.Chat.File.RemoveUpload.Accessibility.label, + progressPercentValue: Localization.Templates.percentValue, + fileNameWithProgressValue: Localization.Templates.fileNameWithProgressValue, isFontScalingEnabled: true ) ) @@ -237,15 +232,13 @@ extension Theme { // TODO: Introduce dedicated localization for Secure conversations upload list instaed of using Chat's one. // MOB-1831 var pickMediaStyle: AttachmentSourceListStyle { - typealias Chat = L10n.Chat.PickMedia - let itemFont = font.bodyText let itemFontColor = color.baseDark let itemIconColor = color.baseDark let pickPhoto = AttachmentSourceItemStyle( kind: .photoLibrary, - title: Chat.photo, + title: Localization.Chat.Attachment.photoLibrary, titleFont: itemFont, titleColor: itemFontColor, icon: Asset.photoLibraryIcon.image, @@ -254,7 +247,7 @@ extension Theme { ) let takePhoto = AttachmentSourceItemStyle( kind: .takePhoto, - title: Chat.takePhoto, + title: Localization.Chat.Attachment.takePhoto, titleFont: itemFont, titleColor: itemFontColor, icon: Asset.cameraIcon.image, @@ -263,7 +256,7 @@ extension Theme { ) let browse = AttachmentSourceItemStyle( kind: .browse, - title: Chat.browse, + title: Localization.General.browse, titleFont: itemFont, titleColor: itemFontColor, icon: Asset.browseIcon.image, @@ -274,13 +267,13 @@ extension Theme { return AttachmentSourceListStyle( items: [pickPhoto, takePhoto, browse], separatorColor: color.baseShade, - backgroundColor: Color.lightGrey + backgroundColor: color.baseNeutral ) } return .init( header: header, - headerTitle: Welcome.header, + headerTitle: Localization.MessageCenter.header, welcomeTitleStyle: welcomeTitleStyle, titleImageStyle: titleImageStyle, welcomeSubtitleStyle: welcomeSubtitleStyle, @@ -292,7 +285,7 @@ extension Theme { filePickerButtonStyle: filePickerButtonStyle, attachmentListStyle: uploadListStyle, pickMediaStyle: pickMediaStyle, - backgroundColor: color.background + backgroundColor: color.baseLight ) } } diff --git a/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Environment.swift b/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Environment.swift index 9c376668c..79f1ecdde 100644 --- a/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Environment.swift +++ b/GliaWidgets/Sources/CallVisualizer/CallVisualizer+Environment.swift @@ -23,5 +23,6 @@ extension CallVisualizer { var assetsBuilder: () -> RemoteConfiguration.AssetsBuilder var getCurrentEngagement: CoreSdkClient.GetCurrentEngagement var eventHandler: ((GliaEvent) -> Void)? + var orientationManager: OrientationManager } } diff --git a/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift b/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift index 4684b8e3a..ad8760831 100644 --- a/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift +++ b/GliaWidgets/Sources/CallVisualizer/CallVisualizer.swift @@ -57,7 +57,8 @@ public final class CallVisualizer { case .maximized: self?.environment.eventHandler?(.maximized) } - } + }, + orientationManager: environment.orientationManager ) ) }() diff --git a/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.Environment.swift b/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.Environment.swift index 079ce7f0d..cfc07aa4c 100644 --- a/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.Environment.swift +++ b/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.Environment.swift @@ -20,5 +20,6 @@ extension CallVisualizer.Coordinator { var date: () -> Date var engagedOperator: () -> CoreSdkClient.Operator? var eventHandler: (DelegateEvent) -> Void + var orientationManager: OrientationManager } } diff --git a/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.swift b/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.swift index e9a4b6f65..302b708ae 100644 --- a/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.swift +++ b/GliaWidgets/Sources/CallVisualizer/Coordinator/CallVisualizer.Coordinator.swift @@ -182,7 +182,8 @@ extension CallVisualizer { let coordinator = ScreenSharingCoordinator( environment: .init( theme: environment.viewFactory.theme, - screenShareHandler: environment.screenShareHandler + screenShareHandler: environment.screenShareHandler, + orientationManager: environment.orientationManager ) ) diff --git a/GliaWidgets/Sources/CallVisualizer/Mock/CallVisualizer.Environment.Mock.swift b/GliaWidgets/Sources/CallVisualizer/Mock/CallVisualizer.Environment.Mock.swift index 34e3d57d0..be3732d4d 100644 --- a/GliaWidgets/Sources/CallVisualizer/Mock/CallVisualizer.Environment.Mock.swift +++ b/GliaWidgets/Sources/CallVisualizer/Mock/CallVisualizer.Environment.Mock.swift @@ -21,7 +21,8 @@ extension CallVisualizer.Environment { engagedOperator: { .mock() }, uiConfig: { nil }, assetsBuilder: { .standard }, - getCurrentEngagement: CoreSdkClient.mock.getCurrentEngagement + getCurrentEngagement: CoreSdkClient.mock.getCurrentEngagement, + orientationManager: .mock() ) } diff --git a/GliaWidgets/Sources/CallVisualizer/Mock/ScreenSharingViewModel.mock.swift b/GliaWidgets/Sources/CallVisualizer/Mock/ScreenSharingViewModel.mock.swift new file mode 100644 index 000000000..e2134370e --- /dev/null +++ b/GliaWidgets/Sources/CallVisualizer/Mock/ScreenSharingViewModel.mock.swift @@ -0,0 +1,19 @@ +#if DEBUG +import SwiftUI + +extension CallVisualizer.ScreenSharingView.Model { + static func mock( + style: ScreenSharingViewStyle = .mock(), + screenSharingHandler: ScreenShareHandler = .mock + ) -> CallVisualizer.ScreenSharingView.Model { + .init( + style: style, + environment: .init( + orientationManager: .mock(), + uiApplication: .mock, + screenShareHandler: screenSharingHandler + ) + ) + } +} +#endif diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Coordinator/ScreenSharingCoordinator+Environment.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Coordinator/ScreenSharingCoordinator+Environment.swift index 07db8aa3c..ae170d9ac 100644 --- a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Coordinator/ScreenSharingCoordinator+Environment.swift +++ b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Coordinator/ScreenSharingCoordinator+Environment.swift @@ -4,5 +4,6 @@ extension CallVisualizer.ScreenSharingCoordinator { struct Environment { let theme: Theme let screenShareHandler: ScreenShareHandler + let orientationManager: OrientationManager } } diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Coordinator/ScreenSharingCoordinator.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Coordinator/ScreenSharingCoordinator.swift index a86068077..75ab11f25 100644 --- a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Coordinator/ScreenSharingCoordinator.swift +++ b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Coordinator/ScreenSharingCoordinator.swift @@ -6,7 +6,7 @@ extension CallVisualizer { var delegate: ((DelegateEvent) -> Void)? private let environment: Environment - private var viewModel: ScreenSharingViewModel? + private var viewModel: ScreenSharingView.Model? var viewController: ScreenSharingViewController? // MARK: - Initialization @@ -24,20 +24,25 @@ extension CallVisualizer { // MARK: - Private private func showEndScreenSharingViewController() -> ViewController { - let viewModel = ScreenSharingViewModel( - style: environment.theme.screenSharing, - environment: .init(screenShareHandler: environment.screenShareHandler) + let environment: ScreenSharingView.Model.Environment = .init( + orientationManager: self.environment.orientationManager, + uiApplication: .live, + screenShareHandler: environment.screenShareHandler + ) + let model: ScreenSharingView.Model = .init( + style: self.environment.theme.screenSharing, + environment: environment ) - self.viewModel = viewModel + self.viewModel = model - let viewController = ScreenSharingViewController(props: viewModel.props()) + let viewController: ScreenSharingViewController = .init(model: model) viewController.modalPresentationStyle = .overFullScreen self.viewController = viewController - viewModel.delegate = .init { [weak self, weak viewController] event in + model.delegate = .init { [weak self, weak viewController] event in switch event { - case .close: + case .closeTapped: viewController?.dismiss(animated: true) self?.delegate?(.close) } @@ -45,19 +50,5 @@ extension CallVisualizer { return viewController } - - private static func createHeaderProps(with header: HeaderStyle) -> Header.Props { - let backButton = header.backButton.map { HeaderButton.Props(style: $0) } - - return .init( - title: "", - effect: .none, - endButton: .init(), - backButton: backButton, - closeButton: .init(style: header.closeButton), - endScreenshareButton: .init(style: header.endScreenShareButton), - style: header - ) - } } } diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Model/ScreenSharingViewModel.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Model/ScreenSharingViewModel.swift new file mode 100644 index 000000000..1be19193a --- /dev/null +++ b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Model/ScreenSharingViewModel.swift @@ -0,0 +1,110 @@ +import SwiftUI +import Combine + +extension CallVisualizer.ScreenSharingView { + final class Model: ObservableObject { + @Published private(set) var orientation: UIInterfaceOrientation + let style: ScreenSharingViewStyle + let environment: Environment + let orientationManager: OrientationManager + var delegate: Command = .nop + var cancellables: Set = [] + + init(style: ScreenSharingViewStyle, environment: Environment) { + self.style = style + self.environment = environment + self.orientationManager = environment.orientationManager + self.orientation = orientationManager.orientation + + orientationManager.$orientation + .sink { [weak self] orientation in + self?.orientation = orientation + } + .store(in: &self.cancellables) + } + } +} + +extension CallVisualizer.ScreenSharingView.Model { + func event(_ event: Event) { + switch event { + case .closeTapped: + delegate(.closeTapped) + case .endScreenShareTapped: + endScreenSharing() + } + } + + func makeHeaderModel() -> HeaderSwiftUI.Model { + let endButtonProps: ActionButtonSwiftUI.Model = .init( + style: style.header.endButton, + accessibilityIdentifier: "header_end_button", + isEnabled: false, + isHidden: true + ) + + var backButton: HeaderButtonSwiftUI.Model? + if let endButtonStyle = style.header.backButton { + backButton = .init( + tap: Cmd { [weak self] in + self?.delegate(.closeTapped) + }, + style: endButtonStyle, + accessibilityIdentifier: "header_back_button", + size: .init(width: 20, height: 20), + isEnabled: true, + isHidden: false + ) + } + + let closeButtonProps: HeaderButtonSwiftUI.Model = .init( + style: style.header.closeButton, + accessibilityIdentifier: "header_close_button", + isEnabled: false, + isHidden: true + ) + + let endScreenShareButtonProps: HeaderButtonSwiftUI.Model = .init( + style: style.header.endScreenShareButton, + accessibilityIdentifier: "header_end_screen_sharing_button", + isEnabled: false, + isHidden: true + ) + + let environment: HeaderSwiftUI.Environment = .init(uiApplication: environment.uiApplication) + + return .init( + title: style.title, + effect: .none, + endButton: endButtonProps, + backButton: backButton, + closeButton: closeButtonProps, + endScreenshareButton: endScreenShareButtonProps, + style: style.header, + environment: environment + ) + } +} + +private extension CallVisualizer.ScreenSharingView.Model { + func endScreenSharing() { + environment.screenShareHandler.stop(nil) + } +} + +extension CallVisualizer.ScreenSharingView.Model { + struct Environment { + let orientationManager: OrientationManager + let uiApplication: UIKitBased.UIApplication + let screenShareHandler: ScreenShareHandler + } + + enum DelegateEvent { + case closeTapped + } + + enum Event { + case closeTapped + case endScreenShareTapped + } +} diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ScreenSharingViewStyle.Accessibility.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Style/ScreenSharingViewStyle.Accessibility.swift similarity index 100% rename from GliaWidgets/Sources/CallVisualizer/ScreenSharing/ScreenSharingViewStyle.Accessibility.swift rename to GliaWidgets/Sources/CallVisualizer/ScreenSharing/Style/ScreenSharingViewStyle.Accessibility.swift diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ScreenSharingViewStyle.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/Style/ScreenSharingViewStyle.swift similarity index 100% rename from GliaWidgets/Sources/CallVisualizer/ScreenSharing/ScreenSharingViewStyle.swift rename to GliaWidgets/Sources/CallVisualizer/ScreenSharing/Style/ScreenSharingViewStyle.swift diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/View/ScreenSharingView.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/View/ScreenSharingView.swift index f6f985d41..4aec32547 100644 --- a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/View/ScreenSharingView.swift +++ b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/View/ScreenSharingView.swift @@ -1,127 +1,59 @@ -import UIKit +import SwiftUI extension CallVisualizer { - final class ScreenSharingView: BaseView { - - // MARK: - Props - - struct Props: Equatable { - let style: ScreenSharingViewStyle - let header: Header.Props - let endScreenSharing: ActionButton.Props - - init( - style: ScreenSharingViewStyle, - header: Header.Props, - endScreenSharing: ActionButton.Props - ) { - self.style = style - self.header = header - self.endScreenSharing = endScreenSharing - } - } - - // MARK: - Properties - - private lazy var header = Header(props: props.header) - .make { header in - header.endScreenShareButton.isHidden = true - header.closeButton.isHidden = true - header.endButton.isHidden = true - } - private lazy var messageLabel = UILabel().make { - $0.font = props.style.messageTextFont - $0.textColor = props.style.messageTextColor - $0.text = props.style.messageText - $0.adjustsFontSizeToFitWidth = true - $0.numberOfLines = 2 - $0.textAlignment = .center - $0.accessibilityIdentifier = "end_screen_sharing_message" - $0.translatesAutoresizingMaskIntoConstraints = false - } - private lazy var endScreenSharingButton = ActionButton(props: props.endScreenSharing).make { - $0.setImage(props.style.buttonIcon, for: .normal) - $0.tintColor = props.style.buttonStyle.titleColor - $0.titleEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 0) - $0.titleLabel?.numberOfLines = 0 - $0.titleLabel?.translatesAutoresizingMaskIntoConstraints = false - $0.accessibilityIdentifier = "end_screen_sharing_button" - $0.accessibilityLabel = L10n.CallVisualizer.ScreenSharing.Accessibility.buttonLabel - $0.accessibilityHint = L10n.CallVisualizer.ScreenSharing.Accessibility.buttonHint - } - private lazy var contentStackView = UIStackView.make( - .vertical, - spacing: 16 - )( - messageLabel, - endScreenSharingButton - ) - - var props: Props { - didSet { - renderProps() - } - } - - // MARK: - Initialization - - init(props: Props) { - self.props = props - super.init() - } - - required init() { - fatalError("init() has not been implemented") - } - - // MARK: - Overrides - - override func setup() { - super.setup() - - addSubview(header) - header.translatesAutoresizingMaskIntoConstraints = false - var constraints = [NSLayoutConstraint](); defer { constraints.activate() } - constraints += header.layoutInSuperview(edges: .horizontal) - - addSubview(contentStackView) - contentStackView.translatesAutoresizingMaskIntoConstraints = false - constraints += contentStackView.centerXAnchor.constraint(equalTo: centerXAnchor) - constraints += contentStackView.centerYAnchor.constraint(equalTo: centerYAnchor) - constraints += contentStackView.layoutIn(layoutMarginsGuide, edges: .horizontal, insets: .init(top: 0, left: 42, bottom: 0, right: 42)) - - constraints += endScreenSharingButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40) - - if let imageView = endScreenSharingButton.imageView { - constraints += imageView.widthAnchor.constraint(equalToConstant: 24) - } - - setFontScalingEnabled( - props.style.accessibility.isFontScalingEnabled, - for: messageLabel - ) - setFontScalingEnabled( - props.style.buttonStyle.accessibility.isFontScalingEnabled, - for: endScreenSharingButton - ) - } - - override func layoutSubviews() { - switch props.style.backgroundColor { - case .fill(let color): - backgroundColor = color - case .gradient(let colors): - makeGradientBackground(colors: colors) + struct ScreenSharingView: View { + @ObservedObject var model: Model + + var body: some View { + ZStack { + Background(model.style.backgroundColor) + .edgesIgnoringSafeArea(.all) + VStack(spacing: 0) { + HeaderSwiftUI(model: model.makeHeaderModel()) + VStack(spacing: 16) { + mainLabel + endScreenShareButton + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + }.edgesIgnoringSafeArea(.top) } } } } -// MARK: - Private +extension CallVisualizer.ScreenSharingView { + var mainLabel: some View { + Text(model.style.messageText) + .font(.convert(model.style.messageTextFont)) + .foregroundColor(SwiftUI.Color(model.style.messageTextColor)) + .lineLimit(2) + .multilineTextAlignment(.center) + .migrationAccessibilityIdentifier("end_screen_sharing_message") + } -private extension CallVisualizer.ScreenSharingView { - func renderProps() { - header.props = props.header - endScreenSharingButton.props = props.endScreenSharing + var endScreenShareButton: some View { + SwiftUI.Button(action: { + model.event(.endScreenShareTapped) + }, label: { + HStack(spacing: 8) { + SwiftUI.Image(uiImage: model.style.buttonIcon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 24) + .foregroundColor(SwiftUI.Color(model.style.buttonStyle.titleColor)) + Text(model.style.buttonStyle.title) + .font(.convert(model.style.buttonStyle.titleFont)) + .foregroundColor(SwiftUI.Color(model.style.buttonStyle.titleColor)) + .lineLimit(nil) + } + .padding(.horizontal, 4) + .frame(maxWidth: .infinity, minHeight: 40, idealHeight: 40) + .background(Background(model.style.buttonStyle.backgroundColor)) + .cornerRadius(4) + .padding(.horizontal, 60) + }) + .migrationAccessibilityIdentifier("end_screen_sharing_button") + .migrationAccessibilityLabel(Localization.ScreenSharing.VisitorScreen.End.title) + .migrationAccessibilityHint(Localization.ScreenSharing.VisitorScreen.End.Accessibility.hint) } } diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewController/ScreenSharingViewController+Props.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewController/ScreenSharingViewController+Props.swift deleted file mode 100644 index a6a8c2969..000000000 --- a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewController/ScreenSharingViewController+Props.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -extension CallVisualizer.ScreenSharingViewController { - struct Props: Equatable { - let screenSharingViewProps: CallVisualizer.ScreenSharingView.Props - } -} diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewController/ScreenSharingViewController.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewController/ScreenSharingViewController.swift index 8d206c6c6..a2e726bff 100644 --- a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewController/ScreenSharingViewController.swift +++ b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewController/ScreenSharingViewController.swift @@ -1,38 +1,42 @@ import UIKit +import SwiftUI extension CallVisualizer { final class ScreenSharingViewController: UIViewController { - private lazy var screenSharingView = ScreenSharingView(props: props.screenSharingViewProps) - private var props: Props + let model: ScreenSharingView.Model // MARK: - Initialization - init(props: Props) { - self.props = props - super.init(nibName: "", bundle: nil) + init( + model: ScreenSharingView.Model + ) { + self.model = model + super.init(nibName: nil, bundle: nil) } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - View lifecycle - override func loadView() { - view = screenSharingView - } - override func viewDidLoad() { super.viewDidLoad() - renderProps() + let hostingController: UIHostingController + let screenSharingView = ScreenSharingView(model: model) + hostingController = UIHostingController(rootView: screenSharingView) + hostingController.willMove(toParent: self) + addChild(hostingController) + view.addSubview(hostingController.view) + hostingController.didMove(toParent: self) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ]) } } } - -// MARK: - Private - -private extension CallVisualizer.ScreenSharingViewController { - func renderProps() { - screenSharingView.props = props.screenSharingViewProps - } -} diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel+Environment.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel+Environment.swift deleted file mode 100644 index 4229ab78a..000000000 --- a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel+Environment.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -extension CallVisualizer.ScreenSharingViewModel { - struct Environment { - let screenShareHandler: ScreenShareHandler - } -} diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel+Output.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel+Output.swift deleted file mode 100644 index eb87e8234..000000000 --- a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel+Output.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -extension CallVisualizer.ScreenSharingViewModel { - enum DelegateEvent { - case close - } -} diff --git a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel.swift b/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel.swift deleted file mode 100644 index 03288bc7f..000000000 --- a/GliaWidgets/Sources/CallVisualizer/ScreenSharing/ViewModel/ScreenSharingViewModel.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation - -extension CallVisualizer { - final class ScreenSharingViewModel { - typealias Props = CallVisualizer.ScreenSharingViewController.Props - - private let environment: Environment - private let style: ScreenSharingViewStyle - - var delegate: Command = .nop - - init( - style: ScreenSharingViewStyle, - environment: Environment - ) { - self.style = style - self.environment = environment - } - } -} - -// MARK: - Private - -extension CallVisualizer.ScreenSharingViewModel { - func props() -> Props { - let backButton = style.header.backButton.map { - HeaderButton.Props( - tap: Cmd { [weak self] in self?.delegate(.close) }, - style: $0 - ) - } - - let headerProps = Header.Props( - title: style.title, - effect: .none, - endButton: .init(style: style.header.endButton), - backButton: backButton, - closeButton: .init( - style: style.header.closeButton - ), - endScreenshareButton: .init( - tap: Cmd { [weak self] in self?.endScreenSharing() }, - style: style.header.endScreenShareButton - ), - style: style.header - ) - let endScreenSharingButtonProps = ActionButton.Props( - style: style.buttonStyle, - tap: Cmd { [weak self] in self?.endScreenSharing() } - ) - - let screenSharingViewProps = CallVisualizer.ScreenSharingView.Props( - style: style, - header: headerProps, - endScreenSharing: endScreenSharingButtonProps - ) - - return Props( - screenSharingViewProps: screenSharingViewProps - ) - } - - func endScreenSharing() { - environment.screenShareHandler.stop(nil) - } -} diff --git a/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.swift b/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.swift index 71b423348..036c104de 100644 --- a/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.swift +++ b/GliaWidgets/Sources/CallVisualizer/VideoCall/ViewModel/VideoCallViewModel.swift @@ -101,7 +101,7 @@ extension CallVisualizer { videoButtonState = .active videoButtonEnabled = true minimizeButtonEnabled = true - title = L10n.Call.Video.title + title = Localization.Engagement.Video.title callDuration = "" topLabelHidden = false endScreenShareButtonHidden = environment.screenShareHandler.status().value == .stopped @@ -487,9 +487,7 @@ private extension CallVisualizer.VideoCallViewModel { private extension CallVisualizer.VideoCallViewModel { func onDurationChanged(_ duration: Int) { - let text = L10n.Call.Connect.Connected.secondText.withCallDuration( - duration.asDurationString - ) + let text = duration.asDurationString callDuration = text } @@ -505,7 +503,7 @@ private extension CallVisualizer.VideoCallViewModel { default: topLabelHidden = true } - title = L10n.Call.Video.title + title = Localization.Engagement.Video.title } updateButtons() } diff --git a/GliaWidgets/Sources/CallVisualizer/VisitorCode/VisitorCodeView.swift b/GliaWidgets/Sources/CallVisualizer/VisitorCode/VisitorCodeView.swift index 1002aa83c..7d06a243a 100644 --- a/GliaWidgets/Sources/CallVisualizer/VisitorCode/VisitorCodeView.swift +++ b/GliaWidgets/Sources/CallVisualizer/VisitorCode/VisitorCodeView.swift @@ -75,8 +75,8 @@ extension CallVisualizer { let refreshButton = UIButton().make { button in button.accessibilityIdentifier = "visitor_code_refresh_button" button.accessibilityTraits = .button - button.accessibilityLabel = L10n.CallVisualizer.VisitorCode.Accessibility.refreshLabel - button.accessibilityHint = L10n.CallVisualizer.VisitorCode.Accessibility.refreshHint + button.accessibilityLabel = Localization.CallVisualizer.VisitorCode.Refresh.Accessibility.label + button.accessibilityHint = Localization.CallVisualizer.VisitorCode.Refresh.Accessibility.hint } let spinnerView = UIImageView().make { imageView in imageView.image = Asset.spinner.image.withRenderingMode(.alwaysTemplate) @@ -102,8 +102,8 @@ extension CallVisualizer { ).make { button in button.accessibilityIdentifier = "visitor_code_alert_close_button" button.accessibilityTraits = .button - button.accessibilityLabel = L10n.CallVisualizer.VisitorCode.Accessibility.closeLabel - button.accessibilityHint = L10n.CallVisualizer.VisitorCode.Accessibility.closeHint + button.accessibilityLabel = Localization.General.Close.accessibility + button.accessibilityHint = Localization.CallVisualizer.VisitorCode.Close.Accessibility.hint } lazy var poweredBy: PoweredBy = PoweredBy(style: props.style.poweredBy) @@ -227,6 +227,7 @@ extension CallVisualizer { rotation.toValue = CGFloat.pi * 2 rotation.duration = 1.0 rotation.repeatCount = Float.infinity + rotation.isRemovedOnCompletion = false spinnerView.tintColor = props.style.loadingProgressColor spinnerView.layer.add(rotation, forKey: "Spin") } @@ -266,15 +267,15 @@ extension CallVisualizer.VisitorCodeView { switch props.viewState { case .success(visitorCode: let code): renderedVisitorCode = code - titleLabel.text = L10n.CallVisualizer.VisitorCode.Title.standard + titleLabel.text = Localization.CallVisualizer.VisitorCode.title renderVisitorCode() - titleLabel.accessibilityHint = L10n.CallVisualizer.VisitorCode.Accessibility.titleHint + titleLabel.accessibilityHint = Localization.CallVisualizer.VisitorCode.Title.Accessibility.hint case .error: - titleLabel.text = L10n.CallVisualizer.VisitorCode.Title.error + titleLabel.text = Localization.VisitorCode.failed renderError() titleLabel.accessibilityHint = nil case .loading: - titleLabel.text = L10n.CallVisualizer.VisitorCode.Title.standard + titleLabel.text = Localization.CallVisualizer.VisitorCode.title renderSpinner() } setFontScalingEnabled( diff --git a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.Mock.swift b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.Mock.swift index 7b00d421d..0c297c735 100644 --- a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.Mock.swift +++ b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.Mock.swift @@ -34,7 +34,8 @@ extension EngagementCoordinator.Environment { startSocketObservation: {}, stopSocketObservation: {}, pushNotifications: .mock, - createSendMessagePayload: { _, _ in .mock() } + createSendMessagePayload: { _, _ in .mock() }, + orientationManager: .mock() ) } #endif diff --git a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.swift b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.swift index 412f4eeea..e59d803ed 100644 --- a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.swift +++ b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.Environment.swift @@ -36,5 +36,6 @@ extension EngagementCoordinator { var stopSocketObservation: CoreSdkClient.StopSocketObservation var pushNotifications: CoreSdkClient.PushNotifications var createSendMessagePayload: CoreSdkClient.CreateSendMessagePayload + var orientationManager: OrientationManager } } diff --git a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift index b68835c0c..5376ac2f8 100644 --- a/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift +++ b/GliaWidgets/Sources/Coordinators/EngagementCoordinator/EngagementCoordinator.swift @@ -452,6 +452,7 @@ extension EngagementCoordinator { uuid: environment.uuid, uiApplication: environment.uiApplication, uiScreen: environment.uiScreen, + uiDevice: environment.uiDevice, notificationCenter: environment.notificationCenter, createFileUploadListModel: environment.createFileUploadListModel, viewFactory: viewFactory, @@ -476,7 +477,8 @@ extension EngagementCoordinator { isAuthenticated: environment.isAuthenticated, startSocketObservation: environment.startSocketObservation, stopSocketObservation: environment.stopSocketObservation, - createSendMessagePayload: environment.createSendMessagePayload + createSendMessagePayload: environment.createSendMessagePayload, + orientationManager: environment.orientationManager ) ) diff --git a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift index 945b82bd2..cb14acb1c 100644 --- a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift +++ b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Interface.swift @@ -6,6 +6,7 @@ struct CoreSdkClient { var pushNotifications: PushNotifications var createAppDelegate: () -> AppDelegate var clearSession: () -> Void + var localeProvider: LocaleProvider typealias FetchVisitorInfo = (_ completion: @escaping (Result) -> Void) -> Void var fetchVisitorInfo: FetchVisitorInfo @@ -171,6 +172,13 @@ extension CoreSdkClient { } } +extension CoreSdkClient { + struct LocaleProvider { + typealias CustomLocaleGetRemoteString = ((String) -> String?) + var getRemoteString: CustomLocaleGetRemoteString + } +} + extension CoreSdkClient { typealias AnswerBlock = GliaCoreSDK.AnswerBlock typealias AnswerWithSuccessBlock = GliaCoreSDK.AnswerWithSuccessBlock diff --git a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Live.swift b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Live.swift index 7da29d155..c4823553a 100644 --- a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Live.swift +++ b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Live.swift @@ -6,6 +6,7 @@ extension CoreSdkClient { pushNotifications: .live, createAppDelegate: Self.AppDelegate.live, clearSession: GliaCore.sharedInstance.clearSession, + localeProvider: .init(getRemoteString: GliaCore.sharedInstance.localeProvider.getRemoteString(_:)), fetchVisitorInfo: GliaCore.sharedInstance.fetchVisitorInfo(_:), updateVisitorInfo: GliaCore.sharedInstance.updateVisitorInfo(_:completion:), configureWithConfiguration: GliaCore.sharedInstance.configure(with:completion:), diff --git a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Mock.swift b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Mock.swift index c091895b0..f73d91be2 100644 --- a/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Mock.swift +++ b/GliaWidgets/Sources/CoreSDKClient/CoreSDKClient.Mock.swift @@ -7,6 +7,7 @@ extension CoreSdkClient { pushNotifications: .mock, createAppDelegate: { .mock }, clearSession: {}, + localeProvider: .mock, fetchVisitorInfo: { _ in }, updateVisitorInfo: { _, _ in }, configureWithConfiguration: { _, _ in }, @@ -53,6 +54,10 @@ extension CoreSdkClient.AppDelegate { ) } +extension CoreSdkClient.LocaleProvider { + static let mock = Self(getRemoteString: { _ in nil }) +} + extension CoreSdkClient.EngagementFile { static func mock(id: String = "") -> CoreSdkClient.EngagementFile { .init(id: id) diff --git a/GliaWidgets/Sources/Extensions/String+TemplateString.swift b/GliaWidgets/Sources/Extensions/String+TemplateString.swift index 4112b220d..d4d28aeca 100644 --- a/GliaWidgets/Sources/Extensions/String+TemplateString.swift +++ b/GliaWidgets/Sources/Extensions/String+TemplateString.swift @@ -1,6 +1,6 @@ extension String { func withOperatorName(_ name: String?) -> String { - let name = name ?? L10n.operator + let name = name ?? Localization.Engagement.defaultOperator return replacingOccurrences(of: "{operatorName}", with: name) } diff --git a/GliaWidgets/Sources/FoundationBased/FoundationBased.Interface.swift b/GliaWidgets/Sources/FoundationBased/FoundationBased.Interface.swift index ef9375c23..1771ad388 100644 --- a/GliaWidgets/Sources/FoundationBased/FoundationBased.Interface.swift +++ b/GliaWidgets/Sources/FoundationBased/FoundationBased.Interface.swift @@ -1,4 +1,5 @@ import Foundation +import Combine enum FoundationBased { struct FileManager { @@ -92,6 +93,8 @@ enum FoundationBased { func removeObserver(_ observer: Any) { removeObserverClosure(observer) } + + var publisherForNotification: (NSNotification.Name) -> AnyPublisher } } diff --git a/GliaWidgets/Sources/FoundationBased/FoundationBased.Live.swift b/GliaWidgets/Sources/FoundationBased/FoundationBased.Live.swift index 1ec27b82f..9b7e3fe29 100644 --- a/GliaWidgets/Sources/FoundationBased/FoundationBased.Live.swift +++ b/GliaWidgets/Sources/FoundationBased/FoundationBased.Live.swift @@ -1,4 +1,5 @@ import Foundation +import Combine extension FoundationBased.FileManager { static let live = Self( @@ -65,6 +66,7 @@ extension FoundationBased.NotificationCenter { static let live = Self( addObserverClosure: NotificationCenter.default.addObserver, removeObserverClosure: NotificationCenter.default.removeObserver, - removeObserverWithNameAndObject: NotificationCenter.default.removeObserver + removeObserverWithNameAndObject: NotificationCenter.default.removeObserver, + publisherForNotification: { NotificationCenter.default.publisher(for: $0).eraseToAnyPublisher() } ) } diff --git a/GliaWidgets/Sources/FoundationBased/FoundationBased.Mock.swift b/GliaWidgets/Sources/FoundationBased/FoundationBased.Mock.swift index c82154df5..032c92f21 100644 --- a/GliaWidgets/Sources/FoundationBased/FoundationBased.Mock.swift +++ b/GliaWidgets/Sources/FoundationBased/FoundationBased.Mock.swift @@ -1,5 +1,6 @@ #if DEBUG import Foundation +import Combine extension FoundationBased.FileManager { static let mock = Self( @@ -50,7 +51,10 @@ extension FoundationBased.NotificationCenter { static let mock = Self( addObserverClosure: { _, _, _, _ in }, removeObserverClosure: { _ in }, - removeObserverWithNameAndObject: { _, _, _ in } + removeObserverWithNameAndObject: { _, _, _ in }, + publisherForNotification: { _ in + Just(Notification(name: NSNotification.Name(rawValue: ""))).eraseToAnyPublisher() + } ) } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift index f50d316d8..efb801dd0 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Interface.swift @@ -44,6 +44,7 @@ extension Glia { var createFileUploadListModel: SecureConversations.FileUploadListViewModel.Create var screenShareHandler: ScreenShareHandler var messagesWithUnreadCountLoaderScheduler: CoreSdkClient.ReactiveSwift.DateScheduler + var orientationManager: OrientationManager } } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift index aeece0fc4..5d96bd07c 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Live.swift @@ -27,7 +27,12 @@ extension Glia.Environment { createFileUploader: FileUploader.init(maximumUploads:environment:), createFileUploadListModel: SecureConversations.FileUploadListViewModel.init, screenShareHandler: ScreenShareHandler.create(), - messagesWithUnreadCountLoaderScheduler: CoreSdkClient.ReactiveSwift.QueueScheduler.main + messagesWithUnreadCountLoaderScheduler: CoreSdkClient.ReactiveSwift.QueueScheduler.main, + orientationManager: .init(environment: .init( + uiApplication: .live, + uiDevice: .live, + notificationCenter: .live + )) ) } diff --git a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift index bc1b20de8..c4d11734d 100644 --- a/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift +++ b/GliaWidgets/Sources/GliaEnvironment/Glia.Environment.Mock.swift @@ -25,7 +25,8 @@ extension Glia.Environment { createFileUploader: FileUploader.mock, createFileUploadListModel: SecureConversations.FileUploadListViewModel.mock(environment:), screenShareHandler: .mock, - messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock + messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, + orientationManager: .mock() ) } diff --git a/GliaWidgets/Sources/RemoteConfiguration/RemoteConfiguration.swift b/GliaWidgets/Sources/RemoteConfiguration/RemoteConfiguration.swift index 4b78ad3cf..c25bb8a3a 100644 --- a/GliaWidgets/Sources/RemoteConfiguration/RemoteConfiguration.swift +++ b/GliaWidgets/Sources/RemoteConfiguration/RemoteConfiguration.swift @@ -20,8 +20,8 @@ extension RemoteConfiguration { let baseLight: String? let baseDark: String? let baseShade: String? - let background: String? let systemNegative: String? + let baseNeutral: String? } } diff --git a/GliaWidgets/Sources/Theme/Survey/Theme+Survey.swift b/GliaWidgets/Sources/Theme/Survey/Theme+Survey.swift index 03b8ec48b..d5238d6bd 100644 --- a/GliaWidgets/Sources/Theme/Survey/Theme+Survey.swift +++ b/GliaWidgets/Sources/Theme/Survey/Theme+Survey.swift @@ -2,7 +2,6 @@ import Foundation import UIKit extension Theme { - public struct SurveyStyle { /// Layer style. public var layer: Layer @@ -53,12 +52,11 @@ extension Theme.SurveyStyle { font: ThemeFont, alertStyle: AlertStyle ) -> Self { - let font = ThemeFontStyle.default.font return .init( layer: .init( - background: .fill(color: color.background), + background: .fill(color: color.baseLight), borderColor: color.baseDark.cgColor, cornerRadius: 30 ), @@ -71,14 +69,14 @@ extension Theme.SurveyStyle { submitButton: .init( actionButtonStyle: alertStyle.positiveAction, accessibility: .init( - label: L10n.Survey.Accessibility.Footer.SubmitButton.label, + label: Localization.General.submit, isFontScalingEnabled: true ) ), cancellButton: .init( actionButtonStyle: alertStyle.negativeAction, accessibility: .init( - label: L10n.Survey.Accessibility.Footer.CancelButton.label, + label: Localization.General.cancel, isFontScalingEnabled: true ) ), diff --git a/GliaWidgets/Sources/Theme/Survey/Theme.Survey.InputQuestion.swift b/GliaWidgets/Sources/Theme/Survey/Theme.Survey.InputQuestion.swift index f164bccf4..6fde7c07d 100644 --- a/GliaWidgets/Sources/Theme/Survey/Theme.Survey.InputQuestion.swift +++ b/GliaWidgets/Sources/Theme/Survey/Theme.Survey.InputQuestion.swift @@ -33,7 +33,7 @@ public extension Theme.SurveyStyle { accessibility: .init(isFontScalingEnabled: true) ), normalLayer: .init( - background: .fill(color: Color.baseLight), + background: .fill(color: color.baseLight), borderColor: color.baseNormal.cgColor, borderWidth: 1, cornerRadius: 4 diff --git a/GliaWidgets/Sources/Theme/Survey/Theme.Survey.ValidationError.swift b/GliaWidgets/Sources/Theme/Survey/Theme.Survey.ValidationError.swift index 80d25e33c..977645e15 100644 --- a/GliaWidgets/Sources/Theme/Survey/Theme.Survey.ValidationError.swift +++ b/GliaWidgets/Sources/Theme/Survey/Theme.Survey.ValidationError.swift @@ -17,11 +17,11 @@ public extension Theme.SurveyStyle { font: ThemeFont ) -> Self { .init( - message: L10n.Survey.Action.validationError, + message: Localization.Survey.Action.validationError, color: color.systemNegative.hex, font: font.caption, accessibility: .init( - label: L10n.Survey.Accessibility.Validation.Title.label, + label: Localization.Survey.Validation.Title.Accessibility.label, isFontScalingEnabled: true ) ) diff --git a/GliaWidgets/Sources/Theme/Theme+Alert.swift b/GliaWidgets/Sources/Theme/Theme+Alert.swift index f3883c527..eeada367f 100644 --- a/GliaWidgets/Sources/Theme/Theme+Alert.swift +++ b/GliaWidgets/Sources/Theme/Theme+Alert.swift @@ -1,30 +1,27 @@ extension Theme { var alertStyle: AlertStyle { - typealias Alert = L10n.Alert - typealias Accessibility = Alert.Accessibility - let negativeAction = ActionButtonStyle( - title: Alert.Action.no, + title: Localization.General.no, titleFont: font.buttonLabel, titleColor: color.baseLight, backgroundColor: .fill(color: color.systemNegative), accessibility: .init( - label: Accessibility.Action.no, + label: Localization.General.no, isFontScalingEnabled: true ) ) let positiveAction = ActionButtonStyle( - title: Alert.Action.yes, + title: Localization.General.yes, titleFont: font.buttonLabel, titleColor: color.baseLight, backgroundColor: .fill(color: color.primary), accessibility: .init( - label: Accessibility.Action.yes, + label: Localization.General.yes, isFontScalingEnabled: true ) ) let poweredBy = PoweredByStyle( - text: L10n.poweredBy, + text: Localization.General.powered, font: font.caption, accessibility: .init(isFontScalingEnabled: true) ) @@ -34,7 +31,7 @@ extension Theme { titleImageColor: color.primary, messageFont: font.bodyText, messageColor: color.baseDark, - backgroundColor: .fill(color: color.background), + backgroundColor: .fill(color: color.baseLight), closeButtonColor: .fill(color: color.baseNormal), actionAxis: .horizontal, positiveAction: positiveAction, diff --git a/GliaWidgets/Sources/Theme/Theme+AlertConfiguration.swift b/GliaWidgets/Sources/Theme/Theme+AlertConfiguration.swift index cf970b5b7..065e36b8d 100644 --- a/GliaWidgets/Sources/Theme/Theme+AlertConfiguration.swift +++ b/GliaWidgets/Sources/Theme/Theme+AlertConfiguration.swift @@ -1,137 +1,135 @@ extension Theme { var alertConfigurationStyle: AlertConfiguration { - typealias Alert = L10n.Alert - let leaveQueue = ConfirmationAlertConfiguration( - title: Alert.LeaveQueue.title, - message: Alert.LeaveQueue.message, - negativeTitle: Alert.Action.no, - positiveTitle: Alert.Action.yes, + title: Localization.Engagement.Queue.Leave.header, + message: Localization.Engagement.Queue.Leave.message, + negativeTitle: Localization.General.no, + positiveTitle: Localization.General.yes, switchButtonBackgroundColors: true, - showsPoweredBy: false + showsPoweredBy: showsPoweredBy ) let endEngagement = ConfirmationAlertConfiguration( - title: Alert.EndEngagement.title, - message: Alert.EndEngagement.message, - negativeTitle: Alert.Action.no, - positiveTitle: Alert.Action.yes, + title: Localization.Engagement.End.Confirmation.header, + message: Localization.Engagement.End.message, + negativeTitle: Localization.General.no, + positiveTitle: Localization.General.yes, switchButtonBackgroundColors: true, - showsPoweredBy: true + showsPoweredBy: showsPoweredBy ) let operatorEndedEngagement = SingleActionAlertConfiguration( - title: Alert.OperatorEndedEngagement.title, - message: Alert.OperatorEndedEngagement.message, - buttonTitle: Alert.Action.ok + title: Localization.Engagement.Ended.header, + message: Localization.Engagement.Ended.message, + buttonTitle: Localization.General.ok ) let endScreenShare = ConfirmationAlertConfiguration( - title: Alert.ScreenSharing.Stop.title, - message: Alert.ScreenSharing.Stop.message, - negativeTitle: Alert.Action.no, - positiveTitle: Alert.Action.yes, + title: Localization.Alert.ScreenSharing.Stop.header, + message: Localization.Alert.ScreenSharing.Stop.message, + negativeTitle: Localization.General.no, + positiveTitle: Localization.General.yes, switchButtonBackgroundColors: true, showsPoweredBy: showsPoweredBy ) let operatorsUnavailable = MessageAlertConfiguration( - title: Alert.OperatorsUnavailable.title, - message: Alert.OperatorsUnavailable.message + title: Localization.Engagement.Queue.Closed.header, + message: Localization.Engagement.Queue.Closed.message ) let audioAction = MediaUpgradeActionStyle( - title: Alert.MediaUpgrade.Audio.title, + title: Localization.Engagement.Audio.title, titleFont: font.header3, titleColor: color.baseDark, - info: Alert.MediaUpgrade.Audio.info, + info: Localization.Engagement.MediaUpgrade.Audio.info, infoFont: font.subtitle, infoColor: color.baseDark, borderColor: color.primary, - backgroundColor: color.background, + backgroundColor: color.baseLight, icon: Asset.upgradeAudio.image, iconColor: color.primary ) let phoneAction = MediaUpgradeActionStyle( - title: Alert.MediaUpgrade.Phone.title, + title: Localization.Engagement.Phone.title, titleFont: font.header3, titleColor: color.baseDark, - info: Alert.MediaUpgrade.Phone.info, + info: Localization.Engagement.MediaUpgrade.Phone.info, infoFont: font.subtitle, infoColor: color.baseDark, borderColor: color.primary, - backgroundColor: color.background, + backgroundColor: color.baseLight, icon: Asset.upgradePhone.image, iconColor: color.primary ) let mediaUpgrade = MultipleMediaUpgradeAlertConfiguration( - title: Alert.MediaUpgrade.title, + title: Localization.Engagement.MediaUpgrade.offer, audioUpgradeAction: audioAction, phoneUpgradeAction: phoneAction, showsPoweredBy: showsPoweredBy ) let audioUpgrade = SingleMediaUpgradeAlertConfiguration( - title: Alert.AudioUpgrade.title, + title: Localization.MediaUpgrade.Audio.title, titleImage: Asset.upgradeAudio.image, - decline: Alert.Action.decline, - accept: Alert.Action.accept, + decline: Localization.General.decline, + accept: Localization.General.accept, showsPoweredBy: showsPoweredBy ) let oneWayVideoUpgrade = SingleMediaUpgradeAlertConfiguration( - title: Alert.VideoUpgrade.OneWay.title, + title: Localization.MediaUpgrade.Video.OneWay.title, titleImage: Asset.upgradeVideo.image, - decline: Alert.Action.decline, - accept: Alert.Action.accept, + decline: Localization.General.decline, + accept: Localization.General.accept, showsPoweredBy: showsPoweredBy ) let twoWayVideoUpgrade = SingleMediaUpgradeAlertConfiguration( - title: Alert.VideoUpgrade.TwoWay.title, + title: Localization.MediaUpgrade.Video.TwoWay.title, titleImage: Asset.upgradeVideo.image, - decline: Alert.Action.decline, - accept: Alert.Action.accept, + decline: Localization.General.decline, + accept: Localization.General.accept, showsPoweredBy: showsPoweredBy ) let screenShareOffer = ScreenShareOfferAlertConfiguration( - title: Alert.ScreenSharing.Start.title, - message: Alert.ScreenSharing.Start.message, + title: Localization.Alert.ScreenSharing.Start.header, + message: Localization.Alert.ScreenSharing.Start.message, titleImage: Asset.startScreenShare.image, - decline: Alert.Action.decline, - accept: Alert.Action.accept, + decline: Localization.General.decline, + accept: Localization.General.accept, showsPoweredBy: showsPoweredBy ) let microphoneSettings = SettingsAlertConfiguration( - title: Alert.MicrophonePermission.title, - message: Alert.MicrophonePermission.message, - settingsTitle: Alert.Action.settings, - cancelTitle: Alert.Action.cancel + title: Localization.Alert.MicrophoneAccess.error, + message: Localization.Ios.Alert.MicrophoneAccess.message, + settingsTitle: Localization.Alert.Action.settings, + cancelTitle: Localization.General.cancel ) let cameraSettings = SettingsAlertConfiguration( - title: Alert.CameraPermission.title, - message: Alert.CameraPermission.message, - settingsTitle: Alert.Action.settings, - cancelTitle: Alert.Action.cancel + title: Localization.Alert.CameraAccess.error, + message: Localization.Ios.Alert.CameraAccess.message, + settingsTitle: Localization.Alert.Action.settings, + cancelTitle: Localization.General.cancel ) let mediaSourceNotAvailable = MessageAlertConfiguration( - title: Alert.MediaSourceNotAvailable.title, - message: Alert.MediaSourceNotAvailable.message + title: Localization.Alert.MediaSourceAccess.error, + message: Localization.Ios.Alert.MediaSource.message ) let unexpected = MessageAlertConfiguration( - title: Alert.Unexpected.title, - message: Alert.Unexpected.message + title: Localization.Error.unexpected, + message: Localization.Engagement.Queue.Reconnection.failed ) let api = MessageAlertConfiguration( - title: Alert.ApiError.title, - message: Alert.ApiError.message + title: Localization.Error.unexpected, + message: Localization.Templates.errorMessage ) let unavailableMessageCenter = MessageAlertConfiguration( - title: Alert.UnavailableMessageCenter.title, - message: Alert.UnavailableMessageCenter.message, + title: Localization.MessageCenter.Unavailable.title, + message: Localization.MessageCenter.Unavailable.message, shouldShowCloseButton: false ) let unavailableMessageCenterForBeingUnauthenticated = MessageAlertConfiguration( - title: Alert.UnavailableMessageCenter.title, - message: Alert.UnavailableMessageCenter.notAuthenticatedMessage, + title: Localization.MessageCenter.Unavailable.title, + message: Localization.MessageCenter.NotAuthenticated.message, shouldShowCloseButton: false ) let unsupportedGvaBroadcastError = MessageAlertConfiguration( - title: L10n.gvaNotSupported, + title: Localization.Gva.UnsupportedAction.error, message: nil ) diff --git a/GliaWidgets/Sources/Theme/Theme+Call.swift b/GliaWidgets/Sources/Theme/Theme+Call.swift index 14bc44232..0cfa02bef 100644 --- a/GliaWidgets/Sources/Theme/Theme+Call.swift +++ b/GliaWidgets/Sources/Theme/Theme+Call.swift @@ -2,9 +2,6 @@ import UIKit extension Theme { var callStyle: CallStyle { - typealias Call = L10n.Call - typealias Accessibility = Call.Accessibility - let onHoldOverlay = OnHoldOverlayStyle( image: Asset.callOnHold.image, imageColor: .fill(color: .white), @@ -14,25 +11,25 @@ extension Theme { image: Asset.back.image, color: color.baseLight, accessibility: .init( - label: Accessibility.Header.BackButton.label, - hint: Accessibility.Header.BackButton.hint + label: Localization.General.back, + hint: Localization.Call.Header.Back.Button.Accessibility.hint ) ) let closeButton = HeaderButtonStyle( image: Asset.close.image, color: color.baseLight, accessibility: .init( - label: Accessibility.Header.CloseButton.label, - hint: Accessibility.Header.CloseButton.hint + label: Localization.General.close, + hint: "" ) ) let endButton = ActionButtonStyle( - title: Call.EndButton.title, + title: Localization.General.end, titleFont: font.buttonLabel, titleColor: color.baseLight, backgroundColor: .fill(color: color.systemNegative), accessibility: .init( - label: Accessibility.Header.EndButton.label, + label: Localization.General.end, isFontScalingEnabled: true ) ) @@ -40,8 +37,8 @@ extension Theme { image: Asset.startScreenShare.image, color: color.baseLight, accessibility: .init( - label: Accessibility.Header.EndScreenShareButton.label, - hint: Accessibility.Header.EndScreenShareButton.hint + label: Localization.General.end, + hint: "" ) ) let header = HeaderStyle( @@ -66,72 +63,72 @@ extension Theme { animationColor: .lightGray, onHoldOverlay: onHoldOverlay, accessibility: .init( - label: Accessibility.Operator.Avatar.label, - hint: Accessibility.Operator.Avatar.hint + label: Localization.Call.OperatorAvatar.Accessibility.label, + hint: Localization.Call.OperatorAvatar.Accessibility.hint ) ) let queue = ConnectStatusStyle( - firstText: Call.Connect.Queue.firstText, + firstText: "", firstTextFont: font.header1, firstTextFontColor: color.baseLight, firstTextStyle: .title1, - secondText: Call.Connect.Queue.secondText, + secondText: Localization.Engagement.ConnectionScreen.message, secondTextFont: font.subtitle, secondTextFontColor: color.baseLight, secondTextStyle: .footnote, accessibility: .init( - firstTextHint: Accessibility.Connect.Queue.FirstText.hint, + firstTextHint: Localization.Call.Connect.FirstText.Accessibility.hint, secondTextHint: nil, isFontScalingEnabled: true ) ) let connecting = ConnectStatusStyle( - firstText: Call.Connect.Connecting.firstText, + firstText: Localization.Engagement.ConnectionScreen.connectWith, firstTextFont: font.header2, firstTextFontColor: color.baseLight, firstTextStyle: .title2, - secondText: Call.Connect.Connecting.secondText, + secondText: "", secondTextFont: font.header2, secondTextFontColor: color.baseLight, secondTextStyle: .title2, accessibility: .init( - firstTextHint: Accessibility.Connect.Connecting.FirstText.hint, + firstTextHint: Localization.Call.Connect.FirstText.Accessibility.hint, secondTextHint: nil, isFontScalingEnabled: true ) ) let connected = ConnectStatusStyle( - firstText: Call.Connect.Connected.firstText, + firstText: Localization.Templates.operatorName, firstTextFont: font.header1, firstTextFontColor: color.baseLight, firstTextStyle: .title1, - secondText: Call.Connect.Connected.secondText, + secondText: Localization.Templates.callDuration, secondTextFont: font.subtitle, secondTextFontColor: color.baseLight, secondTextStyle: .footnote, accessibility: .init( - firstTextHint: Accessibility.Connect.Connected.FirstText.hint, - secondTextHint: Accessibility.Connect.Connected.SecondText.hint, + firstTextHint: Localization.Call.Connect.FirstText.Accessibility.hint, + secondTextHint: Localization.Call.Connect.SecondText.Accessibility.hint, isFontScalingEnabled: true ) ) let onHold = ConnectStatusStyle( - firstText: Call.Connect.Connected.firstText, + firstText: Localization.Templates.operatorName, firstTextFont: font.header1, firstTextFontColor: color.baseLight, firstTextStyle: .title1, - secondText: Call.Connect.Connected.secondText, + secondText: Localization.Templates.callDuration, secondTextFont: font.subtitle, secondTextFontColor: color.baseLight, secondTextStyle: .footnote, accessibility: .init( - firstTextHint: Accessibility.Connect.Connected.FirstText.hint, + firstTextHint: Localization.Call.Connect.FirstText.Accessibility.hint, secondTextHint: nil, isFontScalingEnabled: true ) ) let transferring = ConnectStatusStyle( - firstText: Call.Connect.Transferring.firstText, + firstText: Localization.Engagement.Queue.transferring, firstTextFont: font.header1, firstTextFontColor: color.baseLight, firstTextStyle: .title1, @@ -149,9 +146,9 @@ extension Theme { onHold: onHold ) let onHoldStyle = CallStyle.OnHoldStyle( - onHoldText: Call.OnHold.topText, - descriptionText: Call.OnHold.bottomText, - localVideoStreamLabelText: Call.OnHold.localVideoStreamLabelText, + onHoldText: Localization.Call.OnHold.icon, + descriptionText: Localization.Call.OnHold.bottomText, + localVideoStreamLabelText: Localization.General.you, localVideoStreamLabelFont: font.mediumSubtitle2, localVideoStreamLabelColor: color.baseLight ) @@ -161,35 +158,32 @@ extension Theme { connect: connect, backgroundColor: .fill(color: .white), preferredStatusBarStyle: .lightContent, - audioTitle: Call.Audio.title, - videoTitle: Call.Video.title, - operatorName: Call.Operator.name, + audioTitle: Localization.Engagement.Audio.title, + videoTitle: Localization.Engagement.Video.title, + operatorName: Localization.Templates.operatorName, operatorNameFont: font.header1, operatorNameColor: color.baseLight, durationFont: font.bodyText, durationColor: color.baseLight, - topText: Call.topText, + topText: Localization.Ios.Engagement.ConnectionScreen.videoNotice, topTextFont: font.subtitle, topTextColor: color.baseLight, - bottomText: Call.bottomText, + bottomText: Localization.Engagement.QueueWait.message, bottomTextFont: font.bodyText, bottomTextColor: color.baseLight, buttonBar: buttonBarStyle, onHoldStyle: onHoldStyle, accessibility: .init( - operatorNameHint: Accessibility.OperatorName.hint, - durationHint: Accessibility.CallDuration.hint, - localVideoLabel: Accessibility.Video.Visitor.label, - remoteVideoLabel: Accessibility.Video.Operator.label, + operatorNameHint: Localization.Call.Connect.FirstText.Accessibility.hint, + durationHint: Localization.Call.Connect.SecondText.Accessibility.hint, + localVideoLabel: Localization.Call.VisitorVideo.Accessibility.label, + remoteVideoLabel: Localization.Call.OperatorVideo.Accessibility.label, isFontScalingEnabled: true ) ) } private var buttonBarStyle: CallButtonBarStyle { - typealias Buttons = L10n.Call.Buttons - typealias Accessibility = L10n.Call.Accessibility - let activeBackgroundColor: ColorType = .fill(color: UIColor.white.withAlphaComponent(0.9)) let inactiveBackgroundColor: ColorType = .fill(color: UIColor.black.withAlphaComponent(0.4)) let activeImageColor: ColorType = .fill(color: color.baseDark) @@ -204,42 +198,38 @@ extension Theme { backgroundColor: activeBackgroundColor, image: Asset.callChat.image, imageColor: activeImageColor, - title: Buttons.Chat.title, + title: Localization.Engagement.Chat.title, titleFont: activeTitleFont, titleColor: activeTitleColor, textStyle: .caption1, accessibility: .init( - label: Accessibility.Buttons.Chat.Active.label + label: Localization.General.selected ) ), inactive: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callChat.image, imageColor: inactiveImageColor, - title: Buttons.Chat.title, + title: Localization.Engagement.Chat.title, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Chat.Inactive.label - ) + accessibility: .init(label: "") ), selected: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callChat.image, imageColor: inactiveImageColor, - title: Buttons.Chat.title, + title: Localization.Engagement.Chat.title, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Chat.Inactive.label - ) + accessibility: .init(label: "") ), accessibility: .init( - singleItemBadgeValue: Accessibility.Buttons.Chat.BadgeValue.singleItem, - multipleItemsBadgeValue: Accessibility.Buttons.Chat.BadgeValue.multipleItems, - titleAndBadgeValue: Accessibility.Buttons.Chat.titleAndBadgeValue, + singleItemBadgeValue: Localization.Call.Buttons.Chat.BadgeValue.SingleItem.Accessibility.label, + multipleItemsBadgeValue: Localization.Call.Buttons.Chat.BadgeValue.MultipleItems.Accessibility.label, + titleAndBadgeValue: Localization.Templates.titleAndBadgeValue, isFontScalingEnabled: true ) ) @@ -248,86 +238,79 @@ extension Theme { backgroundColor: activeBackgroundColor, image: Asset.callVideoActive.image, imageColor: activeImageColor, - title: Buttons.Video.title, + title: Localization.Engagement.Video.title, titleFont: activeTitleFont, titleColor: activeTitleColor, textStyle: .caption1, accessibility: .init( - label: Accessibility.Buttons.Video.Active.label + label: Localization.General.selected ) ), inactive: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callVideoInactive.image, imageColor: inactiveImageColor, - title: Buttons.Video.title, + title: Localization.Engagement.Video.title, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Video.Inactive.label - ) + accessibility: .init(label: "") ), selected: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callVideoInactive.image, imageColor: inactiveImageColor, - title: Buttons.Video.title, + title: Localization.Engagement.Video.title, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Video.Inactive.label - ) + accessibility: .init(label: "") ), accessibility: .init( - singleItemBadgeValue: Accessibility.Buttons.Video.BadgeValue.singleItem, - multipleItemsBadgeValue: Accessibility.Buttons.Video.BadgeValue.multipleItems, - titleAndBadgeValue: Accessibility.Buttons.Video.titleAndBadgeValue, + singleItemBadgeValue: "", + multipleItemsBadgeValue: "", + titleAndBadgeValue: Localization.Templates.titleAndBadgeValue, isFontScalingEnabled: true ) ) +#warning("check Localization.Call.Unmute.button") let muteButton = CallButtonStyle( active: CallButtonStyle.StateStyle( backgroundColor: activeBackgroundColor, image: Asset.callMuteActive.image, imageColor: activeImageColor, - title: Buttons.Mute.Active.title, + title: Localization.Call.Unmute.button, titleFont: activeTitleFont, titleColor: activeTitleColor, textStyle: .caption1, accessibility: .init( - label: Accessibility.Buttons.Mute.Active.label + label: Localization.General.selected ) ), inactive: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callMuteInactive.image, imageColor: inactiveImageColor, - title: Buttons.Mute.Inactive.title, + title: Localization.Call.Mute.button, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Mute.Inactive.label - ) + accessibility: .init(label: "") ), selected: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callMuteInactive.image, imageColor: inactiveImageColor, - title: Buttons.Mute.Inactive.title, + title: Localization.Call.Mute.button, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Mute.Inactive.label - ) + accessibility: .init(label: "") ), accessibility: .init( - singleItemBadgeValue: Accessibility.Buttons.Mute.BadgeValue.singleItem, - multipleItemsBadgeValue: Accessibility.Buttons.Mute.BadgeValue.multipleItems, - titleAndBadgeValue: Accessibility.Buttons.Mute.titleAndBadgeValue, + singleItemBadgeValue: "", + multipleItemsBadgeValue: "", + titleAndBadgeValue: Localization.Templates.titleAndBadgeValue, isFontScalingEnabled: true ) ) @@ -336,42 +319,38 @@ extension Theme { backgroundColor: activeBackgroundColor, image: Asset.callSpeakerActive.image, imageColor: activeImageColor, - title: Buttons.Speaker.title, + title: Localization.Call.Speaker.button, titleFont: activeTitleFont, titleColor: activeTitleColor, textStyle: .caption1, accessibility: .init( - label: Accessibility.Buttons.Speaker.Active.label + label: Localization.General.selected ) ), inactive: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callSpeakerInactive.image, imageColor: inactiveImageColor, - title: Buttons.Speaker.title, + title: Localization.Call.Speaker.button, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Speaker.Inactive.label - ) + accessibility: .init(label: "") ), selected: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callSpeakerInactive.image, imageColor: inactiveImageColor, - title: Buttons.Speaker.title, + title: Localization.Call.Speaker.button, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Speaker.Inactive.label - ) + accessibility: .init(label: "") ), accessibility: .init( - singleItemBadgeValue: Accessibility.Buttons.Speaker.BadgeValue.singleItem, - multipleItemsBadgeValue: Accessibility.Buttons.Speaker.BadgeValue.multipleItems, - titleAndBadgeValue: Accessibility.Buttons.Speaker.titleAndBadgeValue, + singleItemBadgeValue: "", + multipleItemsBadgeValue: "", + titleAndBadgeValue: Localization.Templates.titleAndBadgeValue, isFontScalingEnabled: true ) ) @@ -380,42 +359,38 @@ extension Theme { backgroundColor: activeBackgroundColor, image: Asset.callMiminize.image, imageColor: activeImageColor, - title: Buttons.Minimize.title, + title: Localization.Engagement.MinimizeVideo.button, titleFont: activeTitleFont, titleColor: activeTitleColor, textStyle: .caption1, accessibility: .init( - label: Accessibility.Buttons.Minimize.Active.label + label: Localization.General.selected ) ), inactive: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callMiminize.image, imageColor: inactiveImageColor, - title: Buttons.Minimize.title, + title: Localization.Engagement.MinimizeVideo.button, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Minimize.Inactive.label - ) + accessibility: .init(label: "") ), selected: CallButtonStyle.StateStyle( backgroundColor: inactiveBackgroundColor, image: Asset.callMiminize.image, imageColor: inactiveImageColor, - title: Buttons.Minimize.title, + title: Localization.Engagement.MinimizeVideo.button, titleFont: inactiveTitleFont, titleColor: inactiveTitleColor, textStyle: .caption1, - accessibility: .init( - label: Accessibility.Buttons.Minimize.Inactive.label - ) + accessibility: .init(label: "") ), accessibility: .init( - singleItemBadgeValue: Accessibility.Buttons.Minimize.BadgeValue.singleItem, - multipleItemsBadgeValue: Accessibility.Buttons.Minimize.BadgeValue.multipleItems, - titleAndBadgeValue: Accessibility.Buttons.Minimize.titleAndBadgeValue, + singleItemBadgeValue: "", + multipleItemsBadgeValue: "", + titleAndBadgeValue: Localization.Templates.titleAndBadgeValue, isFontScalingEnabled: true ) ) diff --git a/GliaWidgets/Sources/Theme/Theme+Chat.swift b/GliaWidgets/Sources/Theme/Theme+Chat.swift index 1b9ab6663..edad47781 100644 --- a/GliaWidgets/Sources/Theme/Theme+Chat.swift +++ b/GliaWidgets/Sources/Theme/Theme+Chat.swift @@ -1,8 +1,5 @@ extension Theme { var chatStyle: ChatStyle { - typealias Chat = L10n.Chat - typealias Accessibility = Chat.Accessibility - let onHoldOverlay = OnHoldOverlayStyle( image: Asset.callOnHold.image, imageColor: .fill(color: .white), @@ -12,25 +9,25 @@ extension Theme { image: Asset.back.image, color: color.baseLight, accessibility: .init( - label: Accessibility.Header.BackButton.label, - hint: Accessibility.Header.BackButton.hint + label: Localization.General.back, + hint: "" ) ) let closeButton = HeaderButtonStyle( image: Asset.close.image, color: color.baseLight, accessibility: .init( - label: Accessibility.Header.CloseButton.label, - hint: Accessibility.Header.CloseButton.hint + label: Localization.General.close, + hint: "" ) ) let endButton = ActionButtonStyle( - title: Chat.EndButton.title, + title: Localization.General.end, titleFont: font.buttonLabel, titleColor: color.baseLight, backgroundColor: .fill(color: color.systemNegative), accessibility: .init( - label: Accessibility.Header.EndButton.label, + label: Localization.General.end, isFontScalingEnabled: true ) ) @@ -38,8 +35,8 @@ extension Theme { image: Asset.startScreenShare.image, color: color.baseLight, accessibility: .init( - label: Accessibility.Header.EndScreenShareButton.label, - hint: Accessibility.Header.EndScreenShareButton.hint + label: Localization.ScreenSharing.VisitorScreen.End.title, + hint: "" ) ) let header = HeaderStyle( @@ -68,72 +65,72 @@ extension Theme { animationColor: color.primary, onHoldOverlay: onHoldOverlay, accessibility: .init( - label: Accessibility.Operator.Avatar.label, - hint: Accessibility.Operator.Avatar.hint + label: Localization.Chat.OperatorAvatar.Accessibility.label, + hint: Localization.Call.OperatorAvatar.Accessibility.hint ) ) let queue = ConnectStatusStyle( - firstText: Chat.Connect.Queue.firstText, + firstText: "", firstTextFont: font.header1, firstTextFontColor: color.baseDark, firstTextStyle: .title1, - secondText: Chat.Connect.Queue.secondText, + secondText: Localization.Engagement.ConnectionScreen.message, secondTextFont: font.subtitle, secondTextFontColor: color.baseNormal, secondTextStyle: .footnote, accessibility: .init( - firstTextHint: Accessibility.Connect.Queue.FirstText.hint, + firstTextHint: Localization.Chat.OperatorName.Accessibility.label, secondTextHint: nil, isFontScalingEnabled: true ) ) let connecting = ConnectStatusStyle( - firstText: Chat.Connect.Connecting.firstText, + firstText: Localization.Engagement.ConnectionScreen.connectWith, firstTextFont: font.header2, firstTextFontColor: color.baseDark, firstTextStyle: .title2, - secondText: Chat.Connect.Connecting.secondText, + secondText: "", secondTextFont: font.header2, secondTextFontColor: color.baseDark, secondTextStyle: .title2, accessibility: .init( - firstTextHint: Accessibility.Connect.Connecting.FirstText.hint, + firstTextHint: Localization.Chat.OperatorName.Accessibility.label, secondTextHint: nil, isFontScalingEnabled: true ) ) let connected = ConnectStatusStyle( - firstText: Chat.Connect.Connected.firstText, + firstText: Localization.Templates.operatorName, firstTextFont: font.header1, firstTextFontColor: color.baseDark, firstTextStyle: .title1, - secondText: Chat.Connect.Connected.secondText, + secondText: Localization.Chat.OperatorJoined.systemMessage, secondTextFont: font.subtitle, secondTextFontColor: color.primary, secondTextStyle: .footnote, accessibility: .init( - firstTextHint: Accessibility.Connect.Connected.FirstText.hint, + firstTextHint: Localization.Chat.OperatorName.Accessibility.label, secondTextHint: nil, isFontScalingEnabled: true ) ) let onHold = ConnectStatusStyle( - firstText: Chat.Connect.Connected.firstText, + firstText: Localization.Templates.operatorName, firstTextFont: font.header1, firstTextFontColor: color.baseLight, firstTextStyle: .title1, - secondText: Chat.Connect.Connected.secondText, + secondText: Localization.Chat.OperatorJoined.systemMessage, secondTextFont: font.subtitle, secondTextFontColor: color.baseLight, secondTextStyle: .footnote, accessibility: .init( - firstTextHint: Accessibility.Connect.Connected.FirstText.hint, + firstTextHint: Localization.Chat.OperatorName.Accessibility.label, secondTextHint: nil, isFontScalingEnabled: true ) ) let transferring = ConnectStatusStyle( - firstText: Chat.Connect.Transferring.firstText, + firstText: Localization.Engagement.Queue.transferring, firstTextFont: font.header1, firstTextFontColor: color.baseDark, firstTextStyle: .title1, @@ -153,8 +150,8 @@ extension Theme { let visitorImageFile = ChatImageFileContentStyle( backgroundColor: color.primary, accessibility: .init( - contentAccessibilityLabel: Accessibility.Message.attachmentMessageLabel, - youAccessibilityPlaceholder: Accessibility.Message.you, + contentAccessibilityLabel: Localization.Chat.Attachment.Message.Accessibility.label, + youAccessibilityPlaceholder: Localization.General.you, isFontScalingEnabled: true ) ) @@ -179,14 +176,14 @@ extension Theme { textStyle: .caption1, accessibility: .init(isFontScalingEnabled: true) ), - delivered: Chat.Message.Status.delivered, + delivered: Localization.Chat.Message.delivered, accessibility: .init(isFontScalingEnabled: true) ) let operatorImageFile = ChatImageFileContentStyle( - backgroundColor: Color.lightGrey, + backgroundColor: color.baseNeutral, accessibility: .init( - contentAccessibilityLabel: Accessibility.Message.attachmentMessageLabel, - youAccessibilityPlaceholder: Accessibility.Message.you, + contentAccessibilityLabel: Localization.Chat.Attachment.Message.Accessibility.label, + youAccessibilityPlaceholder: Localization.General.you, isFontScalingEnabled: true ) ) @@ -199,7 +196,7 @@ extension Theme { let operatorMessage = OperatorMessageStyle( text: operatorText, background: .init( - background: .fill(color: Color.lightGrey), + background: .fill(color: color.baseNeutral), borderColor: .clear, borderWidth: .zero, cornerRadius: 8.49 @@ -211,18 +208,18 @@ extension Theme { ) let operatorTypingIndicator = OperatorTypingIndicatorStyle( color: color.primary, - accessibility: .init(label: Accessibility.Message.Operator.TypingIndicator.label) + accessibility: .init(label: Localization.Chat.Status.Typing.Accessibility.label) ) let choiceCardImageFile = ChatImageFileContentStyle( backgroundColor: color.baseLight, accessibility: .init( - contentAccessibilityLabel: Accessibility.Message.attachmentMessageLabel, - youAccessibilityPlaceholder: Accessibility.Message.you, + contentAccessibilityLabel: Localization.Chat.Attachment.Message.Accessibility.label, + youAccessibilityPlaceholder: Localization.General.you, isFontScalingEnabled: true ) ) let choiceCardOptionNormalState = Button( - background: .fill(color: Color.lightGrey), + background: .fill(color: color.baseNeutral), title: .init( color: color.baseDark.hex, font: font.bodyText, @@ -231,12 +228,12 @@ extension Theme { ), cornerRadius: 4, accessibility: .init( - label: Accessibility.Message.ChoiceCard.ButtonState.normal, + label: "", isFontScalingEnabled: true ) ) let choiceCardOptionSelectedState = Button( - background: .fill(color: Color.primary), + background: .fill(color: color.primary), title: .init( color: color.baseLight.hex, font: font.bodyText, @@ -245,23 +242,23 @@ extension Theme { ), cornerRadius: 4, accessibility: .init( - label: Accessibility.Message.ChoiceCard.ButtonState.selected, + label: Localization.General.selected, isFontScalingEnabled: true ) ) let choiceCardOptionDisabledState = Button( - background: .fill(color: Color.lightGrey), + background: .fill(color: color.baseNeutral), title: .init( - color: Color.grey.hex, + color: color.baseShade.hex, font: font.bodyText, textStyle: .body, accessibility: .init(isFontScalingEnabled: true) ), cornerRadius: 4, borderWidth: 1, - borderColor: Color.baseShade.toRGBAHex(), + borderColor: color.baseShade.toRGBAHex(), accessibility: .init( - label: Accessibility.Message.ChoiceCard.ButtonState.disabled, + label: Localization.Chat.ChoiceCard.Button.Disabled.Accessibility.label, isFontScalingEnabled: true ) ) @@ -287,61 +284,61 @@ extension Theme { fileDownload: fileDownload, operatorImage: operatorImage, choiceOption: choiceCardOption, - accessibility: .init(imageLabel: Accessibility.Message.ChoiceCard.Image.label) + accessibility: .init(imageLabel: Localization.Chat.ChoiceCard.Image.Accessibility.label) ) let mediaButton = MessageButtonStyle( image: Asset.chatPickMedia.image, color: color.baseNormal, - accessibility: .init(accessibilityLabel: Accessibility.PickMedia.PickAttachmentButton.label) + accessibility: .init(accessibilityLabel: Localization.Chat.attachFiles) ) let sendButton = MessageButtonStyle( image: Asset.chatSend.image, color: color.primary, - accessibility: .init(accessibilityLabel: Accessibility.Message.SendButton.label) + accessibility: .init(accessibilityLabel: Localization.General.send) ) let messageEntry = ChatMessageEntryStyle( messageFont: font.bodyText, messageColor: color.baseDark, - enterMessagePlaceholder: Chat.Message.enterMessagePlaceholder, - startEngagementPlaceholder: Chat.Message.startEngagementPlaceholder, - choiceCardPlaceholder: Chat.Message.choiceCardPlaceholder, + enterMessagePlaceholder: Localization.Chat.Input.placeholder, + startEngagementPlaceholder: Localization.Chat.Message.startEngagementPlaceholder, + choiceCardPlaceholder: Localization.Chat.ChoiceCard.placeholderMessage, placeholderFont: font.bodyText, placeholderColor: color.baseNormal, separatorColor: color.baseShade, - backgroundColor: color.background, + backgroundColor: color.baseLight, mediaButton: mediaButton, sendButton: sendButton, uploadList: uploadListStyle, accessibility: .init( - messageInputAccessibilityLabel: Accessibility.Message.MessageInput.label, + messageInputAccessibilityLabel: Localization.General.message, isFontScalingEnabled: true ) ) let audioUpgrade = ChatCallUpgradeStyle( icon: Asset.upgradeAudio.image, iconColor: color.primary, - text: Chat.Upgrade.Audio.text, + text: Localization.Chat.MediaUpgrade.Audio.systemMessage, textFont: font.bodyText, textColor: color.baseDark, durationFont: font.bodyText, durationColor: color.baseNormal, borderColor: color.baseShade, accessibility: .init( - durationTextHint: Accessibility.ChatCallUpgrade.Audio.Duration.hint, + durationTextHint: Localization.Call.Duration.Accessibility.label, isFontScalingEnabled: true ) ) let videoUpgrade = ChatCallUpgradeStyle( icon: Asset.upgradeVideo.image, iconColor: color.primary, - text: Chat.Upgrade.Video.text, + text: Localization.Chat.MediaUpgrade.Video.systemMessage, textFont: font.bodyText, textColor: color.baseDark, durationFont: font.bodyText, durationColor: color.baseNormal, borderColor: color.baseShade, accessibility: .init( - durationTextHint: Accessibility.ChatCallUpgrade.Video.Duration.hint, + durationTextHint: Localization.Call.Duration.Accessibility.label, isFontScalingEnabled: true ) ) @@ -355,8 +352,8 @@ extension Theme { let callBubble = BubbleStyle( userImage: userImage, accessibility: .init( - label: L10n.Call.Accessibility.Bubble.label, - hint: L10n.Call.Accessibility.Bubble.hint + label: Localization.Call.OperatorAvatar.Accessibility.label, + hint: Localization.Call.Bubble.Accessibility.hint ) ) let unreadMessageIndicator = UnreadMessageIndicatorStyle( @@ -368,21 +365,21 @@ extension Theme { placeholderBackgroundColor: .fill(color: color.primary), imageBackgroundColor: .fill(color: .clear), transferringImage: Asset.operatorTransferring.image, - accessibility: .init(label: Accessibility.Message.UnreadMessagesIndicator.label) + accessibility: .init(label: Localization.Chat.Message.Unread.Accessibility.label) ) let unreadMessageDivider = UnreadMessageDividerStyle( - title: Chat.SecureTranscript.unreadMessageDividerTitle, + title: Localization.Chat.unreadMessageDivider, titleColor: Color.baseNormal, titleFont: font.buttonLabel, - lineColor: Color.primary, + lineColor: color.primary, accessibility: .init(isFontScalingEnabled: true) ) let systemMessage = SystemMessageStyle( text: operatorText, background: Theme.Layer( - background: .fill(color: Color.lightGrey), + background: .fill(color: color.baseNeutral), borderColor: .clear, borderWidth: .zero, cornerRadius: 8.49 @@ -394,9 +391,9 @@ extension Theme { return ChatStyle( header: header, connect: connect, - backgroundColor: .fill(color: color.background), + backgroundColor: .fill(color: color.baseLight), preferredStatusBarStyle: .lightContent, - title: Chat.title, + title: Localization.Engagement.Chat.title, visitorMessageStyle: visitorMessage, operatorMessageStyle: operatorMessage, choiceCardStyle: choiceCard, @@ -408,11 +405,11 @@ extension Theme { unreadMessageIndicator: unreadMessageIndicator, operatorTypingIndicator: operatorTypingIndicator, accessibility: .init( - operator: L10n.operator, - visitor: Accessibility.visitorName, + operator: Localization.Engagement.defaultOperator, + visitor: Localization.General.you, isFontScalingEnabled: true ), - secureTranscriptTitle: Chat.SecureTranscript.headerTitle, + secureTranscriptTitle: Localization.Engagement.SecureMessaging.title, secureTranscriptHeader: secureTranscriptHeader, unreadMessageDivider: unreadMessageDivider, systemMessageStyle: systemMessage, @@ -421,43 +418,40 @@ extension Theme { } private var uploadListStyle: FileUploadListStyle { - typealias Upload = L10n.Chat.Upload - typealias Accessibility = L10n.Chat.Accessibility.Upload - let filePreview = FilePreviewStyle( fileFont: font.subtitle, fileColor: color.baseLight, errorIcon: Asset.uploadError.image, errorIconColor: color.systemNegative, backgroundColor: color.primary, - errorBackgroundColor: Color.lightGrey, + errorBackgroundColor: color.baseNeutral, accessibility: .init(isFontScalingEnabled: true) ) let uploading = FileUploadStateStyle( - text: Upload.uploading, + text: Localization.Chat.File.Upload.inProgress, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.baseNormal ) let uploaded = FileUploadStateStyle( - text: Upload.uploaded, + text: Localization.Chat.File.Upload.success, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.baseNormal ) let error = FileUploadErrorStateStyle( - text: Upload.failed, + text: Localization.Chat.File.Upload.failed, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.systemNegative, - infoFileTooBig: Upload.Error.fileTooBig, - infoUnsupportedFileType: Upload.Error.unsupportedFileType, - infoSafetyCheckFailed: Upload.Error.safetyCheckFailed, - infoNetworkError: Upload.Error.network, - infoGenericError: Upload.Error.generic + infoFileTooBig: Localization.Chat.File.SizeLimit.error, + infoUnsupportedFileType: Localization.Chat.Attachment.unsupportedFile, + infoSafetyCheckFailed: Localization.Chat.File.InfectedFile.error, + infoNetworkError: Localization.Chat.File.Upload.networkError, + infoGenericError: Localization.Chat.File.Upload.genericError ) let upload = FileUploadStyle( filePreview: filePreview, @@ -466,13 +460,13 @@ extension Theme { error: error, progressColor: color.primary, errorProgressColor: color.systemNegative, - progressBackgroundColor: Color.lightGrey, + progressBackgroundColor: color.baseNeutral, removeButtonImage: Asset.uploadRemove.image, removeButtonColor: color.baseNormal, accessibility: .init( - removeButtonAccessibilityLabel: Accessibility.RemoveUpload.label, - progressPercentValue: Accessibility.Progress.percentValue, - fileNameWithProgressValue: Accessibility.Progress.fileNameWithProgressValue, + removeButtonAccessibilityLabel: Localization.Chat.File.RemoveUpload.Accessibility.label, + progressPercentValue: Localization.Templates.percentValue, + fileNameWithProgressValue: Localization.Templates.fileNameWithProgressValue, isFontScalingEnabled: true ) ) @@ -481,49 +475,46 @@ extension Theme { } private var fileDownload: ChatFileDownloadStyle { - typealias Download = L10n.Chat.Download - typealias Accessibility = L10n.Chat.Accessibility - let filePreview = FilePreviewStyle( fileFont: font.subtitle, fileColor: color.baseLight, errorIcon: Asset.uploadError.image, errorIconColor: color.systemNegative, backgroundColor: color.primary, - errorBackgroundColor: Color.lightGrey, + errorBackgroundColor: color.baseNeutral, accessibility: .init(isFontScalingEnabled: true) ) let download = ChatFileDownloadStateStyle( - text: Download.download, + text: Localization.General.download, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.baseNormal ) let downloading = ChatFileDownloadStateStyle( - text: Download.downloading, + text: Localization.Chat.Download.downloading, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.baseNormal ) let open = ChatFileDownloadStateStyle( - text: Download.open, + text: Localization.General.open, font: font.mediumSubtitle2, textColor: color.baseDark, infoFont: font.caption, infoColor: color.baseNormal ) let error = ChatFileDownloadErrorStateStyle( - text: Download.failed, + text: Localization.Chat.Download.failed, font: font.mediumSubtitle2, textColor: color.systemNegative, infoFont: font.caption, infoColor: color.baseNormal, - separatorText: Download.Failed.separator, + separatorText: " | ", separatorFont: font.subtitle, separatorTextColor: color.baseDark, - retryText: Download.Failed.retry, + retryText: Localization.General.retry, retryFont: font.mediumSubtitle2, retryTextColor: color.baseDark ) @@ -536,33 +527,31 @@ extension Theme { error: error, progressColor: color.primary, errorProgressColor: color.systemNegative, - progressBackgroundColor: Color.lightGrey, + progressBackgroundColor: color.baseNeutral, backgroundColor: .white, - borderColor: Color.lightGrey, + borderColor: color.baseNeutral, accessibility: .init( - contentAccessibilityLabel: Accessibility.Message.attachmentMessageLabel, - youAccessibilityPlaceholder: Accessibility.Message.you, + contentAccessibilityLabel: Localization.Chat.Attachment.Message.Accessibility.label, + youAccessibilityPlaceholder: Localization.General.you, isFontScalingEnabled: true ), downloadAccessibility: .init( - noneState: Accessibility.Download.State.none, - downloadingState: Accessibility.Download.State.downloading, - downloadedState: Accessibility.Download.State.downloaded, - errorState: Accessibility.Download.State.error + noneState: Localization.Templates.downloadWithFileState, + downloadingState: Localization.Templates.downloadWithFileStateAndPercentValue, + downloadedState: Localization.Templates.downloadWithFileState, + errorState: Localization.Templates.downloadWithFileState ) ) } private var pickMedia: AttachmentSourceListStyle { - typealias Chat = L10n.Chat.PickMedia - let itemFont = font.bodyText let itemFontColor = color.baseDark let itemIconColor = color.baseDark let pickPhoto = AttachmentSourceItemStyle( kind: .photoLibrary, - title: Chat.photo, + title: Localization.Chat.Attachment.photoLibrary, titleFont: itemFont, titleColor: itemFontColor, icon: Asset.photoLibraryIcon.image, @@ -571,7 +560,7 @@ extension Theme { ) let takePhoto = AttachmentSourceItemStyle( kind: .takePhoto, - title: Chat.takePhoto, + title: Localization.Chat.Attachment.takePhoto, titleFont: itemFont, titleColor: itemFontColor, icon: Asset.cameraIcon.image, @@ -580,7 +569,7 @@ extension Theme { ) let browse = AttachmentSourceItemStyle( kind: .browse, - title: Chat.browse, + title: Localization.General.browse, titleFont: itemFont, titleColor: itemFontColor, icon: Asset.browseIcon.image, @@ -591,7 +580,7 @@ extension Theme { return AttachmentSourceListStyle( items: [pickPhoto, takePhoto, browse], separatorColor: color.baseShade, - backgroundColor: Color.lightGrey + backgroundColor: color.baseNeutral ) } } diff --git a/GliaWidgets/Sources/Theme/Theme+Gva.swift b/GliaWidgets/Sources/Theme/Theme+Gva.swift index 789ffc1cd..6f7fd7e70 100644 --- a/GliaWidgets/Sources/Theme/Theme+Gva.swift +++ b/GliaWidgets/Sources/Theme/Theme+Gva.swift @@ -20,14 +20,14 @@ extension Theme { ), accessibility: .init(isFontScalingEnabled: true) ), - backgroundColor: .fill(color: color.lightGrey), + backgroundColor: .fill(color: color.baseNeutral), cornerRadius: 10, borderWidth: 0, borderColor: .clear, button: .init( textFont: font.caption, - textColor: .black, - backgroundColor: .fill(color: color.background), + textColor: color.baseDark, + backgroundColor: .fill(color: color.baseLight), cornerRadius: 5, borderColor: .clear, borderWidth: 0, @@ -37,16 +37,16 @@ extension Theme { let quickReplyButton: GvaQuickReplyButtonStyle = .init( textFont: font.buttonLabel, - textColor: Color.primary, - backgroundColor: .fill(color: Color.baseLight), + textColor: color.primary, + backgroundColor: .fill(color: color.baseLight), cornerRadius: 10, - borderColor: Color.primary, + borderColor: color.primary, borderWidth: 1 ) let galleryCard: GvaGalleryCardStyle = .init( cardContainer: .init( - backgroundColor: .fill(color: color.lightGrey), + backgroundColor: .fill(color: color.baseNeutral), cornerRadius: 8, borderColor: .clear, borderWidth: 0 @@ -59,22 +59,22 @@ extension Theme { ), title: .init( font: font.mediumSubtitle1, - textColor: .black, + textColor: color.baseDark, textStyle: .body ), subtitle: .init( font: font.caption, - textColor: .black, + textColor: color.baseDark, textStyle: .caption1 ), button: .init( title: .init( font: font.caption, - textColor: .black, + textColor: color.baseDark, textStyle: .caption1 ), background: .init( - backgroundColor: .fill(color: color.background), + backgroundColor: .fill(color: color.baseLight), cornerRadius: 8, borderColor: .clear, borderWidth: 0 diff --git a/GliaWidgets/Sources/Theme/Theme+MinimizedBubble.swift b/GliaWidgets/Sources/Theme/Theme+MinimizedBubble.swift index 816d77d4b..2a00933ad 100644 --- a/GliaWidgets/Sources/Theme/Theme+MinimizedBubble.swift +++ b/GliaWidgets/Sources/Theme/Theme+MinimizedBubble.swift @@ -22,8 +22,8 @@ extension Theme { badge: badge, onHoldOverlay: onHoldOverlay, accessibility: .init( - label: L10n.Call.Accessibility.Bubble.label, - hint: L10n.Call.Accessibility.Bubble.hint + label: Localization.Call.Bubble.Accessibility.label, + hint: Localization.Call.Bubble.Accessibility.hint ) ) } diff --git a/GliaWidgets/Sources/Theme/Theme+ScreenSharing.swift b/GliaWidgets/Sources/Theme/Theme+ScreenSharing.swift index b0867ed12..dcd45dce0 100644 --- a/GliaWidgets/Sources/Theme/Theme+ScreenSharing.swift +++ b/GliaWidgets/Sources/Theme/Theme+ScreenSharing.swift @@ -3,19 +3,19 @@ import UIKit extension Theme { var screenSharingStyle: ScreenSharingViewStyle { return ScreenSharingViewStyle( - title: L10n.CallVisualizer.ScreenSharing.title, + title: Localization.CallVisualizer.ScreenSharing.Header.title, header: chat.header, - messageText: L10n.CallVisualizer.ScreenSharing.message, + messageText: Localization.CallVisualizer.ScreenSharing.message, messageTextFont: font.header2, messageTextColor: color.baseDark, buttonStyle: .init( - title: L10n.CallVisualizer.ScreenSharing.Button.title, + title: Localization.ScreenSharing.VisitorScreen.End.title, titleFont: font.buttonLabel, titleColor: color.baseLight, backgroundColor: .fill(color: color.systemNegative), cornerRaidus: 4, accessibility: .init( - label: L10n.CallVisualizer.ScreenSharing.Button.title, + label: Localization.ScreenSharing.VisitorScreen.End.title, isFontScalingEnabled: true ) ), diff --git a/GliaWidgets/Sources/Theme/Theme+VisitorCode.swift b/GliaWidgets/Sources/Theme/Theme+VisitorCode.swift index 3fa7cd594..aff09be1e 100644 --- a/GliaWidgets/Sources/Theme/Theme+VisitorCode.swift +++ b/GliaWidgets/Sources/Theme/Theme+VisitorCode.swift @@ -3,8 +3,8 @@ import UIKit extension Theme { var visitorCodeStyle: VisitorCodeStyle { let numberSlot = NumberSlotStyle( - backgroundColor: .fill(color: color.background), - borderColor: UIColor.ultraLightGray, + backgroundColor: .fill(color: color.baseLight), + borderColor: color.baseNeutral, borderWidth: 1, cornerRadius: 8, numberFont: font.header1, @@ -13,14 +13,14 @@ extension Theme { ) let actionButton = ActionButtonStyle( - title: L10n.CallVisualizer.VisitorCode.Action.refresh, + title: Localization.General.refresh, titleFont: font.buttonLabel, titleColor: color.baseLight, backgroundColor: .fill(color: color.primary) ) let poweredBy = PoweredByStyle( - text: L10n.poweredBy, + text: Localization.General.powered, font: font.caption, accessibility: .init(isFontScalingEnabled: true) ) @@ -31,7 +31,7 @@ extension Theme { poweredBy: poweredBy, numberSlot: numberSlot, actionButton: actionButton, - backgroundColor: .fill(color: color.background), + backgroundColor: .fill(color: color.baseLight), cornerRadius: 30, closeButtonColor: .fill(color: color.baseNormal), loadingProgressColor: color.primary, @@ -39,7 +39,3 @@ extension Theme { ) } } - -private extension UIColor { - static let ultraLightGray = UIColor(red: 0.953, green: 0.953, blue: 0.953, alpha: 1) -} diff --git a/GliaWidgets/Sources/Theme/Theme.swift b/GliaWidgets/Sources/Theme/Theme.swift index 97ce15437..dc1f06f6a 100644 --- a/GliaWidgets/Sources/Theme/Theme.swift +++ b/GliaWidgets/Sources/Theme/Theme.swift @@ -91,8 +91,8 @@ public class Theme { baseLight: config.globalColors?.baseLight.map { UIColor(hex: $0) }, baseDark: config.globalColors?.baseDark.map { UIColor(hex: $0) }, baseShade: config.globalColors?.baseShade.map { UIColor(hex: $0) }, - background: config.globalColors?.background.map { UIColor(hex: $0) }, - systemNegative: config.globalColors?.systemNegative.map { UIColor(hex: $0) } + systemNegative: config.globalColors?.systemNegative.map { UIColor(hex: $0) }, + baseNeutral: config.globalColors?.baseNeutral.map { UIColor(hex: $0) } ) ) ) diff --git a/GliaWidgets/Sources/Theme/ThemeColor.swift b/GliaWidgets/Sources/Theme/ThemeColor.swift index 59f9f351a..061a676b4 100644 --- a/GliaWidgets/Sources/Theme/ThemeColor.swift +++ b/GliaWidgets/Sources/Theme/ThemeColor.swift @@ -20,14 +20,11 @@ public struct ThemeColor { /// Base shade color. By default used as a separator color between message input area and chat, in attachment source list and as a border color in media upgrae prompts. public var baseShade: UIColor - /// Background color. By default used as a background color for chat, message input area and alerts. - public var background: UIColor - /// Negative system color. By default used as a background color for "End Engagement" button, negative action button in alerts and as file download/upload error icon, progress bar and text color. public var systemNegative: UIColor /// Light grey color. By default used as a background for gva persistent buttons and gallery cards. - public var lightGrey: UIColor + public var baseNeutral: UIColor /// /// - Parameters: @@ -37,7 +34,6 @@ public struct ThemeColor { /// - baseLight: Base light color. By default used as a text color in chat/call view title, visitor chat message, "End Engagement" button, queue/connection views and operator name in calls, alert titles and some other labels. /// - baseDark: Base dark color. By default used as a text color in chat queue/connect views, operator chat messages, choice cards, message entry area, upgrade prompts, attachment source list and some other labels. /// - baseShade: Base shade color. By default used as a separator color between message input area and chat, in attachment source list and as a border color in media upgrae prompts. - /// - background: Background color. By default used as a background color for chat, message input area and alerts. /// - systemNegative: Negative system color. By default used as a background color for "End Engagement" button, negative action button in alerts and as file download/upload error icon, progress bar and text color. public init( primary: UIColor? = nil, @@ -46,9 +42,8 @@ public struct ThemeColor { baseLight: UIColor? = nil, baseDark: UIColor? = nil, baseShade: UIColor? = nil, - background: UIColor? = nil, systemNegative: UIColor? = nil, - lightGrey: UIColor? = nil + baseNeutral: UIColor? = nil ) { self.primary = primary ?? Color.primary self.secondary = secondary ?? Color.secondary @@ -56,8 +51,7 @@ public struct ThemeColor { self.baseLight = baseLight ?? Color.baseLight self.baseDark = baseDark ?? Color.baseDark self.baseShade = baseShade ?? Color.baseShade - self.background = background ?? Color.background self.systemNegative = systemNegative ?? Color.systemNegative - self.lightGrey = lightGrey ?? Color.lightGrey + self.baseNeutral = baseNeutral ?? Color.baseNeutral } } diff --git a/GliaWidgets/Sources/UIKitBased/UIKitBased.Interface.swift b/GliaWidgets/Sources/UIKitBased/UIKitBased.Interface.swift index 81bcc5349..c1f8a3ab4 100644 --- a/GliaWidgets/Sources/UIKitBased/UIKitBased.Interface.swift +++ b/GliaWidgets/Sources/UIKitBased/UIKitBased.Interface.swift @@ -17,6 +17,7 @@ enum UIKitBased { struct UIDevice { var proximityState: () -> Bool var isProximityMonitoringEnabled: (Bool) -> Void + var orientationDidChangeNotification: () -> NSNotification.Name } struct UIScreen { diff --git a/GliaWidgets/Sources/UIKitBased/UIKitBased.Live.swift b/GliaWidgets/Sources/UIKitBased/UIKitBased.Live.swift index 51ab52b4a..7527cf763 100644 --- a/GliaWidgets/Sources/UIKitBased/UIKitBased.Live.swift +++ b/GliaWidgets/Sources/UIKitBased/UIKitBased.Live.swift @@ -18,7 +18,8 @@ extension UIKitBased.UIApplication { extension UIKitBased.UIDevice { static let live = Self.init( proximityState: { UIDevice.current.proximityState }, - isProximityMonitoringEnabled: { UIDevice.current.isProximityMonitoringEnabled = $0 } + isProximityMonitoringEnabled: { UIDevice.current.isProximityMonitoringEnabled = $0 }, + orientationDidChangeNotification: { UIDevice.orientationDidChangeNotification } ) } diff --git a/GliaWidgets/Sources/UIKitBased/UIKitBased.Mock.swift b/GliaWidgets/Sources/UIKitBased/UIKitBased.Mock.swift index 458f5c3d8..3c0eb769d 100644 --- a/GliaWidgets/Sources/UIKitBased/UIKitBased.Mock.swift +++ b/GliaWidgets/Sources/UIKitBased/UIKitBased.Mock.swift @@ -29,7 +29,8 @@ extension UIImage { extension UIKitBased.UIDevice { static let mock = Self.init( proximityState: { .init() }, - isProximityMonitoringEnabled: { _ in } + isProximityMonitoringEnabled: { _ in }, + orientationDidChangeNotification: { .init("") } ) } diff --git a/GliaWidgets/Sources/View/Chat/ChatView.swift b/GliaWidgets/Sources/View/Chat/ChatView.swift index 07492095e..1874607b4 100644 --- a/GliaWidgets/Sources/View/Chat/ChatView.swift +++ b/GliaWidgets/Sources/View/Chat/ChatView.swift @@ -31,7 +31,7 @@ class ChatView: EngagementView { private lazy var quickReplyView = QuickReplyView( style: style.gliaVirtualAssistant.quickReplyButton ) - private var messageEntryViewBottomConstraint: NSLayoutConstraint! + private var messageEntryViewBottomConstraint: NSLayoutConstraint? private var callBubble: BubbleView? private let keyboardObserver = KeyboardObserver() private let kUnreadMessageIndicatorInset: CGFloat = -3 @@ -172,7 +172,9 @@ class ChatView: EngagementView { edges: .bottom, insets: messageEntryInsets ).first - constraints += messageEntryViewBottomConstraint + if let messageEntryViewBottomConstraint { + constraints += messageEntryViewBottomConstraint + } constraints += messageEntryView.layoutIn(safeAreaLayoutGuide, edges: .horizontal) constraints += messageEntryView.topAnchor.constraint(equalTo: quickReplyView.bottomAnchor) @@ -584,15 +586,18 @@ extension ChatView { extension ChatView { private func observeKeyboard() { - keyboardObserver.keyboardWillShow = { [unowned self] properties in - let bottomInset = safeAreaInsets.bottom + keyboardObserver.keyboardWillShow = { [weak self] properties in + guard let self else { return } + + let bottomInset = self.safeAreaInsets.bottom let newEntryConstraint = -properties.finalFrame.height + bottomInset + UIView.animate( withDuration: properties.duration, delay: 0.0, options: properties.animationOptions, animations: { [weak self] in - self?.messageEntryViewBottomConstraint.constant = newEntryConstraint + self?.messageEntryViewBottomConstraint?.constant = newEntryConstraint self?.layoutIfNeeded() }, completion: { [weak self] _ in @@ -601,13 +606,13 @@ extension ChatView { ) } - keyboardObserver.keyboardWillHide = { [unowned self] properties in + keyboardObserver.keyboardWillHide = { [weak self] properties in UIView.animate( withDuration: properties.duration, delay: 0.0, options: properties.animationOptions, animations: { [weak self] in - self?.messageEntryViewBottomConstraint.constant = 0 + self?.messageEntryViewBottomConstraint?.constant = 0 self?.layoutIfNeeded() } ) diff --git a/GliaWidgets/Sources/View/Chat/Entry/ChatMessageEntryView.swift b/GliaWidgets/Sources/View/Chat/Entry/ChatMessageEntryView.swift index da7df154b..a7ed2c828 100644 --- a/GliaWidgets/Sources/View/Chat/Entry/ChatMessageEntryView.swift +++ b/GliaWidgets/Sources/View/Chat/Entry/ChatMessageEntryView.swift @@ -287,7 +287,6 @@ extension ChatMessageEntryView { enum MediaPickerButtonVisibility { enum ToggledBy { - case choiceCard case enagagementConnection(isConnected: Bool) case secureMessaging } @@ -300,8 +299,6 @@ extension MediaPickerButtonVisibility { switch self { case .disabled: return true - case .enabled(.choiceCard): - return true case let .enabled(.enagagementConnection(isConnected)): return !isConnected case .enabled(.secureMessaging): diff --git a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift index 4af4c20d0..f0d35516e 100644 --- a/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Call/CallViewController.Mock.swift @@ -43,6 +43,7 @@ extension CallViewController { startWith: startAction ) let theme = Theme.mock() + theme.call.connect.queue.firstText = "CompanyName" let viewFactEnv = ViewFactory.Environment.mock let viewFactory: ViewFactory = .mock( theme: theme, @@ -217,6 +218,7 @@ extension CallViewController { startWith: startAction ) let theme = Theme.mock() + theme.call.connect.queue.firstText = "CompanyName" let viewFactEnv = ViewFactory.Environment.mock let viewFactory: ViewFactory = .mock( theme: theme, diff --git a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.swift b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.swift index b70a25503..488ac4eb8 100644 --- a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.swift +++ b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.swift @@ -168,6 +168,8 @@ final class ChatViewController: EngagementViewController, PopoverPresenter { view?.messageEntryView.uploadListView.props = fileUploadListProps case let .quickReplyPropsUpdated(props): view?.renderQuickReply(props: props) + case .transcript(.messageCenterAvailabilityUpdated): + break } self?.renderProps() } diff --git a/GliaWidgets/Sources/ViewController/Survey/Components/ButtonView/Survey.ButtonView.swift b/GliaWidgets/Sources/ViewController/Survey/Components/ButtonView/Survey.ButtonView.swift index c439b6e45..edaedb075 100644 --- a/GliaWidgets/Sources/ViewController/Survey/Components/ButtonView/Survey.ButtonView.swift +++ b/GliaWidgets/Sources/ViewController/Survey/Components/ButtonView/Survey.ButtonView.swift @@ -16,9 +16,11 @@ extension Survey { var label: String { switch state { case .selected: - return L10n.Survey.Accessibility.Question.OptionButton.Selected.label.withButtonTitle(title) + return Localization.Survey.Question.OptionButton.Selected.Accessibility.label + .withButtonTitle(title) case .active, .highlighted: - return L10n.Survey.Accessibility.Question.OptionButton.Unselected.label.withButtonTitle(title) + return Localization.Survey.Question.OptionButton.Unselected.Accessibility.label + .withButtonTitle(title) } } return .init(label: label) diff --git a/GliaWidgets/Sources/ViewController/Survey/Components/CheckboxView/Survey.Checkbox.swift b/GliaWidgets/Sources/ViewController/Survey/Components/CheckboxView/Survey.Checkbox.swift index c33a5fb04..9ea8f0614 100644 --- a/GliaWidgets/Sources/ViewController/Survey/Components/CheckboxView/Survey.Checkbox.swift +++ b/GliaWidgets/Sources/ViewController/Survey/Components/CheckboxView/Survey.Checkbox.swift @@ -16,9 +16,11 @@ extension Survey { var label: String { switch state { case .selected: - return L10n.Survey.Accessibility.Question.OptionButton.Selected.label.withButtonTitle(title) + return Localization.Survey.Question.OptionButton.Selected.Accessibility.label + .withButtonTitle(title) case .active, .highlighted: - return L10n.Survey.Accessibility.Question.OptionButton.Unselected.label.withButtonTitle(title) + return Localization.Survey.Question.OptionButton.Unselected.Accessibility.label + .withButtonTitle(title) } } return .init(label: label) diff --git a/GliaWidgets/Sources/ViewController/Survey/Components/SingleChoiceQuestionView/Survey.SingleChoiceQuestionView.swift b/GliaWidgets/Sources/ViewController/Survey/Components/SingleChoiceQuestionView/Survey.SingleChoiceQuestionView.swift index 79225afce..aac938310 100644 --- a/GliaWidgets/Sources/ViewController/Survey/Components/SingleChoiceQuestionView/Survey.SingleChoiceQuestionView.swift +++ b/GliaWidgets/Sources/ViewController/Survey/Components/SingleChoiceQuestionView/Survey.SingleChoiceQuestionView.swift @@ -85,9 +85,12 @@ extension Survey { zip(props.options, optionsStack.arrangedSubviews) .forEach { opt, view in guard let checkboxView = view as? CheckboxView else { return } + + let state = Self.handleSelection(with: props, option: opt) + checkboxView.props = .init( title: opt.name, - state: props.selected == opt ? .selected : .active + state: state ) { opt.select(opt) } @@ -95,6 +98,22 @@ extension Survey { validationError.isHidden = !props.showValidationError } + static func handleSelection( + with props: Props, + option opt: Survey.Option + ) -> CheckboxView.State { + // If user selected or it should be selected by default. + let isSelected = props.selected == opt || props.defaultOption == opt + + // Trigger selection manually because the option has + // been selected by default, not because of user input. + if isSelected, props.selected == nil { + opt.select(opt) + } + + return isSelected ? .selected : .active + } + // MARK: - Private private let style: Theme.SurveyStyle.SingleQuestion @@ -109,6 +128,7 @@ extension Survey.SingleChoiceQuestionView { var showValidationError: Bool var options: [Survey.Option] var selected: Survey.Option? + var defaultOption: Survey.Option? var answerContainer: CoreSdkClient.SurveyAnswerContainer? let accessibility: Accessibility @@ -124,6 +144,7 @@ extension Survey.SingleChoiceQuestionView { showValidationError: Bool = false, options: [Survey.Option] = [], selected: Survey.Option? = nil, + defaultOption: Survey.Option? = nil, answerContainer: CoreSdkClient.SurveyAnswerContainer? = nil, accessibility: Accessibility ) { @@ -133,6 +154,7 @@ extension Survey.SingleChoiceQuestionView { self.showValidationError = showValidationError self.options = options self.selected = selected + self.defaultOption = defaultOption self.answerContainer = answerContainer self.accessibility = accessibility } diff --git a/GliaWidgets/Sources/ViewController/Survey/Mocks/Survey.ViewController.Props.Mock.swift b/GliaWidgets/Sources/ViewController/Survey/Mocks/Survey.ViewController.Props.Mock.swift index 3b89f2a80..e63ffbb74 100644 --- a/GliaWidgets/Sources/ViewController/Survey/Mocks/Survey.ViewController.Props.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Survey/Mocks/Survey.ViewController.Props.Mock.swift @@ -16,6 +16,23 @@ extension Survey.ViewController.Props { ) } + static func emptyPropsMockWithDefaultValue() -> Survey.ViewController.Props { + return Survey.ViewController.Props( + header: "Survey title", + props: [ + makeScalePropsMock(), + makeInputPropsMock(), + makeBooleanPropsMock(), + makeSinglePropsMock(defaultOption: .init( + name: "Second option", + value: "\(2)" + )) + ], + submit: { _ in }, + cancel: {} + ) + } + static func filledPropsMock() -> Survey.ViewController.Props { return Survey.ViewController.Props( header: "Survey title", @@ -26,7 +43,7 @@ extension Survey.ViewController.Props { )), makeInputPropsMock(), makeBooleanPropsMock(selectedOption: .init( - name: L10n.Survey.Action.yes, + name: Localization.General.yes, value: true )), makeSinglePropsMock(selectedOption: .init( @@ -90,8 +107,8 @@ private extension Survey.ViewController.Props { accessibility: .init(value: "Required") ) props.options = [ - .init(name: L10n.Survey.Action.yes, value: true), - .init(name: L10n.Survey.Action.no, value: false) + .init(name: Localization.General.yes, value: true), + .init(name: Localization.General.no, value: false) ].compactMap { $0 } props.selected = selectedOption return props @@ -99,6 +116,7 @@ private extension Survey.ViewController.Props { static func makeSinglePropsMock( selectedOption: Survey.Option? = nil, + defaultOption: Survey.Option? = nil, showValidationError: Bool = false ) -> Survey.SingleChoiceQuestionView.Props { var props = Survey.SingleChoiceQuestionView.Props( @@ -113,6 +131,7 @@ private extension Survey.ViewController.Props { .init(name: "Second option", value: "\(2)"), .init(name: "Third option", value: "\(3)") ].compactMap { $0 } + props.defaultOption = defaultOption props.selected = selectedOption return props } diff --git a/GliaWidgets/Sources/ViewController/Survey/Survey.ViewController.Props.swift b/GliaWidgets/Sources/ViewController/Survey/Survey.ViewController.Props.swift index 4629d89ba..0b75dacf0 100644 --- a/GliaWidgets/Sources/ViewController/Survey/Survey.ViewController.Props.swift +++ b/GliaWidgets/Sources/ViewController/Survey/Survey.ViewController.Props.swift @@ -123,7 +123,7 @@ extension Survey.ViewController.Props { ) -> Survey.ScaleQuestionView.Props { let accessibilityValue = sdkQuestion.required - ? L10n.Survey.Accessibility.Question.Title.value + ? Localization.Survey.Question.Required.Accessibility.label : nil var scaleProps = Survey.ScaleQuestionView.Props( @@ -163,7 +163,7 @@ extension Survey.ViewController.Props { ) -> Survey.BooleanQuestionView.Props { let accessibilityValue = sdkQuestion.required - ? L10n.Survey.Accessibility.Question.Title.value + ? Localization.Survey.Question.Required.Accessibility.label : nil var booleanProps = Survey.BooleanQuestionView.Props( @@ -183,8 +183,8 @@ extension Survey.ViewController.Props { updateProps(newViewControllerProps) } booleanProps.options = [ - .init(name: L10n.Survey.Action.yes, value: true, select: handleBooleanOptionSelection), - .init(name: L10n.Survey.Action.no, value: false, select: handleBooleanOptionSelection) + .init(name: Localization.General.yes, value: true, select: handleBooleanOptionSelection), + .init(name: Localization.General.no, value: false, select: handleBooleanOptionSelection) ] return booleanProps @@ -200,13 +200,27 @@ extension Survey.ViewController.Props { ) -> Survey.SingleChoiceQuestionView.Props { let accessibilityValue = sdkQuestion.required - ? L10n.Survey.Accessibility.Question.Title.value + ? Localization.Survey.Question.Required.Accessibility.label : nil + let defaultOption: Survey.Option? = { + let defaultOption = sdkQuestion.options?.first { + $0.isDefault == true + } + + guard let defaultOption else { return nil } + + return .init( + name: defaultOption.label, + value: defaultOption.id.rawValue + ) + }() + var scaleProps = Survey.SingleChoiceQuestionView.Props( id: sdkQuestion.id.rawValue, title: sdkQuestion.text, isRequired: sdkQuestion.required, + defaultOption: defaultOption, accessibility: .init(value: accessibilityValue) ) let handleSingleOptionSelection = { (option: Survey.Option) in @@ -241,7 +255,7 @@ extension Survey.ViewController.Props { ) -> Survey.InputQuestionView.Props { let accessibilityValue = sdkQuestion.required - ? L10n.Survey.Accessibility.Question.Title.value + ? Localization.Survey.Question.Required.Accessibility.label : nil var inputProps = Survey.InputQuestionView.Props( @@ -250,7 +264,7 @@ extension Survey.ViewController.Props { isRequired: sdkQuestion.required, accessibility: .init( titleValue: accessibilityValue, - fieldHint: L10n.Survey.Accessibility.Question.TextField.hint + fieldHint: Localization.Survey.Question.Input.Accessibility.hint ) ) inputProps.textDidChange = { newValue in diff --git a/GliaWidgets/Sources/ViewController/Survey/SurveyViewController.View.swift b/GliaWidgets/Sources/ViewController/Survey/SurveyViewController.View.swift index 670d027f0..f10ee44be 100644 --- a/GliaWidgets/Sources/ViewController/Survey/SurveyViewController.View.swift +++ b/GliaWidgets/Sources/ViewController/Survey/SurveyViewController.View.swift @@ -30,7 +30,7 @@ extension Survey { $0.layer.shadowRadius = 8 } let cancelButton = UIButton(type: .custom).make { - $0.setTitle(L10n.Survey.Action.cancel, for: .normal) + $0.setTitle(Localization.General.cancel, for: .normal) $0.titleLabel?.numberOfLines = 0 // Using `byWordWrapping` prevents button text // from getting truncated, for example for large @@ -38,7 +38,7 @@ extension Survey { $0.titleLabel?.lineBreakMode = .byWordWrapping } let submitButton = UIButton(type: .custom).make { - $0.setTitle(L10n.Survey.Action.submit, for: .normal) + $0.setTitle(Localization.General.submit, for: .normal) $0.titleLabel?.numberOfLines = 0 $0.titleLabel?.lineBreakMode = .byWordWrapping } @@ -201,7 +201,7 @@ extension NSAttributedString { if isRequired { mutableString.append( .init( - string: L10n.Survey.Question.Title.asterisk, + string: "*", attributes: [ .foregroundColor: UIColor.red, .font: font diff --git a/GliaWidgets/Sources/ViewFactory/ViewFactory.swift b/GliaWidgets/Sources/ViewFactory/ViewFactory.swift index 0dfb41c53..5ed46237b 100644 --- a/GliaWidgets/Sources/ViewFactory/ViewFactory.swift +++ b/GliaWidgets/Sources/ViewFactory/ViewFactory.swift @@ -136,8 +136,4 @@ class ViewFactory { ) -> SecureConversations.WelcomeView { return .init(props: props, environment: environment) } - - func makeSecureConversationsConfirmationView(props: SecureConversations.ConfirmationView.Props) -> SecureConversations.ConfirmationView { - return .init(props: props) - } } diff --git a/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift b/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift index e20c27693..86a705cb5 100644 --- a/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/Call/CallViewModel.swift @@ -2,8 +2,6 @@ import Foundation import UIKit class CallViewModel: EngagementViewModel, ViewModel { - private typealias Strings = L10n.Call - var action: ((Action) -> Void)? var delegate: ((DelegateEvent) -> Void)? @@ -74,7 +72,20 @@ class CallViewModel: EngagementViewModel, ViewModel { super.start() update(for: call.kind.value) - update(for: interactor.state) + + // In the case when SDK is configured once and then + // visitor has several Audio/Video engagements in a raw, + // after ending each of them, `interactor.state` has `.ended` value, + // which causes calling `call.end()` on the start of the next + // Audio/Video engagement. That `call.end()` breaks the flow and SDK does not + // handle `connected` state properly. So we need to skip handling `.ended` state + // on the start of a new engagement. + switch interactor.state { + case .ended: + break + default: + update(for: interactor.state) + } switch startWith { case .engagement(let mediaType): @@ -95,11 +106,7 @@ class CallViewModel: EngagementViewModel, ViewModel { action?(.queue) case .engaged: showConnecting() - - let operatorName = Strings.Operator.name.withOperatorName( - interactor.engagedOperator?.firstName - ) - + let operatorName = interactor.engagedOperator?.firstName ?? Localization.Engagement.defaultOperator action?(.setOperatorName(operatorName)) case .ended: call.end() @@ -139,7 +146,7 @@ class CallViewModel: EngagementViewModel, ViewModel { private func update(for callKind: CallKind) { switch callKind { case .audio: - action?(.setTitle(Strings.Audio.title)) + action?(.setTitle(Localization.Engagement.Audio.title)) action?(.setTopTextHidden(true)) case .video(let direction): switch direction { @@ -150,7 +157,7 @@ class CallViewModel: EngagementViewModel, ViewModel { action?(.setTopTextHidden(true)) } - action?(.setTitle(Strings.Video.title)) + action?(.setTitle(Localization.Engagement.Video.title)) } updateButtons() } @@ -391,9 +398,7 @@ extension CallViewModel { } private func onDurationChanged(_ duration: Int) { - let text = Strings.Connect.Connected.secondText.withCallDuration( - duration.asDurationString - ) + let text = duration.asDurationString action?(.setCallDurationText(text)) } } diff --git a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ViewModel.swift b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ViewModel.swift index 90e7493d8..5f58d2668 100644 --- a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel+ViewModel.swift @@ -1,8 +1,6 @@ import Foundation extension ChatViewModel: ViewModel { - typealias Strings = L10n.Chat - enum Event { case viewDidLoad case messageTextChanged(String) @@ -23,6 +21,10 @@ extension ChatViewModel: ViewModel { } enum Action { + /// Actions specific for `TranscriptModel`. + enum TranscriptAction { + case messageCenterAvailabilityUpdated + } case queue case connected(name: String?, imageUrl: String?) case transferring @@ -55,6 +57,7 @@ extension ChatViewModel: ViewModel { case setAttachmentButtonVisibility(MediaPickerButtonVisibility) case fileUploadListPropsUpdated(SecureConversations.FileUploadListView.Props) case quickReplyPropsUpdated(QuickReplyView.Props) + case transcript(TranscriptAction) } enum DelegateEvent { diff --git a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift index c1a20fbde..3f1495faf 100644 --- a/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift @@ -58,7 +58,6 @@ class ChatViewModel: EngagementViewModel { guard environment.getCurrentEngagement() != nil else { return .enabled(.enagagementConnection(isConnected: false)) } - guard !isChoiceCardInputModeEnabled else { return .enabled(.choiceCard) } return .enabled(.enagagementConnection(isConnected: environment.getCurrentEngagement() != nil)) } @@ -313,6 +312,7 @@ extension ChatViewModel { extension ChatViewModel { private func onEngagementTransferring() { action?(.setMessageEntryEnabled(false)) + action?(.setAttachmentButtonVisibility(.disabled)) appendItem(.init(kind: .transferring), to: messagesSection, animated: true) action?(.scrollToBottom(animated: true)) endScreenSharing() @@ -320,6 +320,7 @@ extension ChatViewModel { private func onEngagementTransferred() { action?(.setMessageEntryEnabled(true)) + action?(.setAttachmentButtonVisibility(mediaPickerButtonVisibility)) let engagedOperator = interactor.engagedOperator action?(.setCallBubbleImage(imageUrl: engagedOperator?.picture?.url)) @@ -663,6 +664,16 @@ extension ChatViewModel { return } + // Discard pending message delivered via socket to + // avoid message duplication. Currently pending messages + // stay in pending section whole session. + for pendingMessage in self.pendingSection.items { + if case let .outgoingMessage(outgoingPendingMessage) = pendingMessage.kind, + outgoingPendingMessage.payload.messageId.rawValue.uppercased() == message.id.uppercased() { + return + } + } + registerReceivedMessage(messageId: message.id) let receivedMessage = message diff --git a/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift b/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift index e1e615749..26e843a84 100644 --- a/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift +++ b/GliaWidgets/Sources/ViewModel/EngagementViewModel.swift @@ -11,6 +11,8 @@ class EngagementViewModel: CommonEngagementModel { let alertConfiguration: AlertConfiguration let environment: Environment let screenShareHandler: ScreenShareHandler + // Need to keep strong reference of `activeEngagement`, + // to be able to fetch survey after ending engagement var activeEngagement: CoreSdkClient.Engagement? private(set) var isViewActive = ObservableValue(with: false) @@ -225,10 +227,10 @@ class EngagementViewModel: CommonEngagementModel { } func endSession() { - interactor.endSession { - self.engagementDelegate?(.finished) - } failure: { _ in - self.engagementDelegate?(.finished) + interactor.endSession { [weak self] in + self?.engagementDelegate?(.finished) + } failure: { [weak self] _ in + self?.engagementDelegate?(.finished) } self.screenShareHandler.stop(nil) } diff --git a/GliaWidgets/StaticValues.swift b/GliaWidgets/StaticValues.swift index 076753be1..2f70c850c 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.1.0" + static let sdkVersion = "2.2.0" } diff --git a/GliaWidgets/StringProviding.swift b/GliaWidgets/StringProviding.swift new file mode 100644 index 000000000..cf21cda81 --- /dev/null +++ b/GliaWidgets/StringProviding.swift @@ -0,0 +1,5 @@ +import Foundation + +struct StringProviding { + var getRemoteString: ((String) -> String?) +} diff --git a/GliaWidgets/SwiftUI/Components/BackgroundSwiftUI.swift b/GliaWidgets/SwiftUI/Components/BackgroundSwiftUI.swift new file mode 100644 index 000000000..bbce1869d --- /dev/null +++ b/GliaWidgets/SwiftUI/Components/BackgroundSwiftUI.swift @@ -0,0 +1,24 @@ +import SwiftUI + +struct Background: View { + let colorType: ColorType + + init(_ colorType: ColorType) { + self.colorType = colorType + } + var body: some View { + switch colorType { + case .fill(let color): + SwiftUI.Color(color) + case .gradient(let colors): + let convertedColors = colors + .map { UIColor(cgColor: $0) } + .map { SwiftUI.Color($0) } + LinearGradient( + colors: convertedColors, + startPoint: .top, + endPoint: .bottom + ) + } + } +} diff --git a/GliaWidgets/SwiftUI/Components/Buttons/ActionButtonSwiftUI.swift b/GliaWidgets/SwiftUI/Components/Buttons/ActionButtonSwiftUI.swift new file mode 100644 index 000000000..377436d00 --- /dev/null +++ b/GliaWidgets/SwiftUI/Components/Buttons/ActionButtonSwiftUI.swift @@ -0,0 +1,65 @@ +import SwiftUI + +struct ActionButtonSwiftUI: View { + static let edgeInsets: EdgeInsets = .init(top: 6, leading: 16, bottom: 6, trailing: 16) + @ObservedObject var model: Model + + var body: some View { + Text(model.style.title) + .font(.convert(model.style.titleFont)) + .foregroundColor(SwiftUI.Color(model.style.titleColor)) + .padding(Self.edgeInsets) + .background(SwiftUI.Color(model.style.backgroundColor.color)) + .cornerRadius(model.style.cornerRaidus ?? 0) + .overlay( + RoundedRectangle( + cornerRadius: model.style.cornerRaidus ?? 0) + .stroke( + SwiftUI.Color(model.style.borderColor ?? .clear), + lineWidth: model.style.borderWidth ?? 0 + ) + ) + .shadow( + color: SwiftUI.Color(model.style.shadowColor ?? .clear), + radius: model.style.shadowRadius ?? 0, + x: model.style.shadowOffset?.width ?? 0, + y: model.style.shadowOffset?.height ?? 0 + ) + .migrationAccessibilityIdentifier(model.accessibilityIdentifier) + .migrationAccessibilityAddTrait(.isButton) + .migrationAccessibilityRemoveTrait(.isImage) + .onTapGesture(perform: model.tap.execute) + } +} + +extension ActionButtonSwiftUI { + final class Model: ObservableObject { + let style: ActionButtonStyle + let height: CGFloat + let tap: Cmd + let accessibilityIdentifier: String + @Published var isEnabled: Bool + @Published var isHidden: Bool + + init( + style: ActionButtonStyle = .init( + title: "", + titleFont: .systemFont(ofSize: 16), + titleColor: .white, + backgroundColor: .fill(color: .blue) + ), + height: CGFloat = 40, + tap: Cmd = .nop, + accessibilityIdentifier: String = "", + isEnabled: Bool, + isHidden: Bool + ) { + self.style = style + self.height = height + self.tap = tap + self.accessibilityIdentifier = accessibilityIdentifier.isEmpty ? style.title : accessibilityIdentifier + self.isEnabled = isEnabled + self.isHidden = isHidden + } + } +} diff --git a/GliaWidgets/SwiftUI/Components/Buttons/HeaderButtonSwiftUI.swift b/GliaWidgets/SwiftUI/Components/Buttons/HeaderButtonSwiftUI.swift new file mode 100644 index 000000000..e72df6cea --- /dev/null +++ b/GliaWidgets/SwiftUI/Components/Buttons/HeaderButtonSwiftUI.swift @@ -0,0 +1,52 @@ +import SwiftUI + +struct HeaderButtonSwiftUI: View { + @ObservedObject var model: Model + + var body: some View { + SwiftUI.Image(uiImage: model.style.image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame( + width: model.size.width, + height: model.size.height + ) + .padding(.horizontal, 8) + .padding(.vertical, 6) + .contentShape(Rectangle()) + .foregroundColor(SwiftUI.Color(model.style.color)) + .opacity(model.isEnabled ? 1.0 : 0.6) + .migrationAccessibilityLabel(model.style.accessibility.label) + .migrationAccessibilityAddTrait(.isButton) + .migrationAccessibilityRemoveTrait(.isImage) + .migrationAccessibilityIdentifier(model.accessibilityIdentifier) + .onTapGesture(perform: model.tap.execute) + } +} + +extension HeaderButtonSwiftUI { + final class Model: ObservableObject { + var tap: Cmd + var style: HeaderButtonStyle + var accessibilityIdentifier: String + var size: CGSize + var isEnabled: Bool + var isHidden: Bool + + init( + tap: Cmd = .nop, + style: HeaderButtonStyle, + accessibilityIdentifier: String, + size: CGSize = CGSize(width: 14, height: 14), + isEnabled: Bool, + isHidden: Bool + ) { + self.tap = tap + self.style = style + self.size = size + self.isEnabled = isEnabled + self.isHidden = isHidden + self.accessibilityIdentifier = accessibilityIdentifier + } + } +} diff --git a/GliaWidgets/SwiftUI/Components/Header/HeaderSwiftUI.swift b/GliaWidgets/SwiftUI/Components/Header/HeaderSwiftUI.swift new file mode 100644 index 000000000..f0c436937 --- /dev/null +++ b/GliaWidgets/SwiftUI/Components/Header/HeaderSwiftUI.swift @@ -0,0 +1,86 @@ +import SwiftUI + +struct HeaderSwiftUI: View { + @ObservedObject var model: Model + + var body: some View { + ZStack(alignment: .bottom) { + HStack(spacing: 16) { + if let backButton = model.backButton { + HeaderButtonSwiftUI(model: backButton) + } + + Spacer() + if !model.endButton.isHidden { + ActionButtonSwiftUI(model: model.endButton) + } + + if !model.endScreenshareButton.isHidden { + HeaderButtonSwiftUI(model: model.endScreenshareButton) + } + + if !model.closeButton.isHidden { + HeaderButtonSwiftUI(model: model.closeButton) + } + } + Text(model.title) + .font(.convert(model.style.titleFont)) + .foregroundColor(SwiftUI.Color(model.style.titleColor)) + .migrationAccessibilityIdentifier("header_view_title_label") + .migrationAccessibilityLabel(model.title) + .migrationAccessibilityAddTrait(.isHeader) + } + .padding(.horizontal, 16) + .padding(.bottom, 12) + .frame( + height: 58 + (model.environment.uiApplication.windows().first?.safeAreaInsets.top ?? 0), + alignment: .bottom + ) + .background(SwiftUI.Color(model.style.backgroundColor.color)) + .edgesIgnoringSafeArea([.leading, .trailing]) + } +} + +extension HeaderSwiftUI { + final class Model: ObservableObject { + let title: String + let effect: Effect + let endButton: ActionButtonSwiftUI.Model + let backButton: HeaderButtonSwiftUI.Model? + let closeButton: HeaderButtonSwiftUI.Model + let endScreenshareButton: HeaderButtonSwiftUI.Model + let style: HeaderStyle + let environment: Environment + + init( + title: String, + effect: Effect, + endButton: ActionButtonSwiftUI.Model, + backButton: HeaderButtonSwiftUI.Model?, + closeButton: HeaderButtonSwiftUI.Model, + endScreenshareButton: HeaderButtonSwiftUI.Model, + style: HeaderStyle, + environment: Environment + ) { + self.title = title + self.effect = effect + self.endButton = endButton + self.backButton = backButton + self.closeButton = closeButton + self.endScreenshareButton = endScreenshareButton + self.style = style + self.environment = environment + } + } +} + +extension HeaderSwiftUI { + enum Effect { + case none + case blur + } + + struct Environment { + let uiApplication: UIKitBased.UIApplication + } +} diff --git a/GliaWidgets/SwiftUI/Extensions/Font+Extensions.swift b/GliaWidgets/SwiftUI/Extensions/Font+Extensions.swift new file mode 100644 index 000000000..7b20072ab --- /dev/null +++ b/GliaWidgets/SwiftUI/Extensions/Font+Extensions.swift @@ -0,0 +1,7 @@ +import SwiftUI + +extension Font { + static func convert(_ uiFont: UIFont) -> Font { + return .custom(uiFont.familyName + uiFont.fontName, size: uiFont.pointSize) + } +} diff --git a/GliaWidgets/SwiftUI/Extensions/View+Accessibility.swift b/GliaWidgets/SwiftUI/Extensions/View+Accessibility.swift new file mode 100644 index 000000000..583181a50 --- /dev/null +++ b/GliaWidgets/SwiftUI/Extensions/View+Accessibility.swift @@ -0,0 +1,112 @@ +import SwiftUI + +/// Accessibility identifier view modifier for resolving between +/// deprecated and new method to specify accessibility identifier. +struct MigrationAccessibilityIdentifierModifier: ViewModifier { + let identifier: String + + func body(content: Content) -> some View { + if #available(iOS 14, *) { + content.accessibilityIdentifier(identifier) + } else { + content.accessibility(identifier: identifier) + } + } +} + +/// Accessibility label view modifier for resolving between +/// deprecated and new method to specify accessibility label. +struct MigrationAccessibilityLabelModifier: ViewModifier { + let label: String + + func body(content: Content) -> some View { + if #available(iOS 14, *) { + content.accessibilityLabel(label) + } else { + content.accessibility(label: .init(label)) + } + } +} + +/// Accessibility hint view modifier for resolving between +/// deprecated and new method to specify accessibility hint. +struct MigrationAccessibilityHintModifier: ViewModifier { + let hint: String + + func body(content: Content) -> some View { + if #available(iOS 14, *) { + content.accessibilityHint(hint) + } else { + content.accessibility(hint: .init(hint)) + } + } +} + +/// Accessibility addTraits view modifier for resolving between +/// deprecated and new method to add accessibility traits. +struct MigrationAccessibilityAddTraitModifier: ViewModifier { + let trait: AccessibilityTraits + + func body(content: Content) -> some View { + if #available(iOS 14, *) { + content.accessibilityAddTraits(trait) + } else { + content.accessibility(addTraits: trait) + } + } +} + +/// Accessibility removeTraits view modifier for resolving between +/// deprecated and new method to accessibility traits. +struct MigrationAccessibilityDropTraitModifier: ViewModifier { + let trait: AccessibilityTraits + + func body(content: Content) -> some View { + if #available(iOS 14, *) { + content.accessibilityRemoveTraits(trait) + } else { + content.accessibility(removeTraits: trait) + } + } +} + +/// Accessibility hidden view modifier for resolving between +/// deprecated and new method to hidden or show accessibility. +struct MigrationAccessibilityHiddenModifier: ViewModifier { + let isHidden: Bool + + func body(content: Content) -> some View { + if #available(iOS 14, *) { + content.accessibilityHidden(isHidden) + } else { + content.accessibility(hidden: isHidden) + } + } +} + +/// Modifiers for specifying accessibility for avoiding deprecation warning. +extension View { + func migrationAccessibilityIdentifier(_ identifier: String) -> some View { + self.modifier(MigrationAccessibilityIdentifierModifier(identifier: identifier)) + } + + func migrationAccessibilityLabel(_ label: String) -> some View { + self.modifier(MigrationAccessibilityLabelModifier(label: label)) + } + + func migrationAccessibilityHint(_ hint: String) -> some View { + self.modifier(MigrationAccessibilityHintModifier(hint: hint)) + } + + func migrationAccessibilityAddTrait(_ trait: AccessibilityTraits) -> some View { + self.modifier(MigrationAccessibilityAddTraitModifier(trait: trait)) + } + + func migrationAccessibilityRemoveTrait(_ trait: AccessibilityTraits) -> some View { + self.modifier(MigrationAccessibilityDropTraitModifier(trait: trait)) + } + + func migrationAccessibilityHidden(_ isHidden: Bool) -> some View { + self.modifier(MigrationAccessibilityHiddenModifier(isHidden: isHidden)) + } +} diff --git a/GliaWidgets/SwiftUI/Managers/OrientationManager/OrientationManager.Mock.swift b/GliaWidgets/SwiftUI/Managers/OrientationManager/OrientationManager.Mock.swift new file mode 100644 index 000000000..0a0879eb3 --- /dev/null +++ b/GliaWidgets/SwiftUI/Managers/OrientationManager/OrientationManager.Mock.swift @@ -0,0 +1,19 @@ +import Foundation + +#if DEBUG + +extension OrientationManager { + static func mock( + uiApplication: UIKitBased.UIApplication = .mock, + uiDevice: UIKitBased.UIDevice = .mock, + notificationCenter: FoundationBased.NotificationCenter = .mock + ) -> OrientationManager { + .init(environment: .init( + uiApplication: uiApplication, + uiDevice: uiDevice, + notificationCenter: notificationCenter + )) + } +} + +#endif diff --git a/GliaWidgets/SwiftUI/Managers/OrientationManager/OrientationManager.swift b/GliaWidgets/SwiftUI/Managers/OrientationManager/OrientationManager.swift new file mode 100644 index 000000000..a3f626c69 --- /dev/null +++ b/GliaWidgets/SwiftUI/Managers/OrientationManager/OrientationManager.swift @@ -0,0 +1,38 @@ +import SwiftUI +import Combine + +final class OrientationManager: ObservableObject { + @Published private(set) var orientation: UIInterfaceOrientation + + private let environment: Environment + private var orientationSubscription: AnyCancellable? + private var currentOrientation: UIInterfaceOrientation { + environment.uiApplication.windows().first?.windowScene?.interfaceOrientation ?? .portrait + } + + var isPortrait: Bool { + orientation == .portrait || orientation == .portraitUpsideDown + } + + var isLandscape: Bool { + !isPortrait + } + + init(environment: Environment) { + self.environment = environment + orientation = environment.uiApplication.windows().first?.windowScene?.interfaceOrientation ?? .portrait + orientationSubscription = environment.notificationCenter + .publisherForNotification(environment.uiDevice.orientationDidChangeNotification()) + .map { _ in self.currentOrientation } + .removeDuplicates() + .assign(to: \.orientation, on: self) + } +} + +extension OrientationManager { + struct Environment { + var uiApplication: UIKitBased.UIApplication + var uiDevice: UIKitBased.UIDevice + var notificationCenter: FoundationBased.NotificationCenter + } +} diff --git a/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingView.Mock.swift b/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingView.Mock.swift deleted file mode 100644 index 37436e052..000000000 --- a/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingView.Mock.swift +++ /dev/null @@ -1,15 +0,0 @@ -#if DEBUG - -import UIKit - -extension CallVisualizer.ScreenSharingView.Props { - static func mock( - style: ScreenSharingViewStyle = .mock(), - header: Header.Props = .mock(), - endScreenSharing: ActionButton.Props = .mock() - ) -> CallVisualizer.ScreenSharingView.Props { - return .init(style: style, header: header, endScreenSharing: endScreenSharing) - } -} - -#endif diff --git a/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingViewController.Mock.swift b/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingViewController.Mock.swift deleted file mode 100644 index e9c084e3c..000000000 --- a/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingViewController.Mock.swift +++ /dev/null @@ -1,13 +0,0 @@ -#if DEBUG - -import UIKit - -extension CallVisualizer.ScreenSharingViewController.Props { - static func mock( - screenShareViewProps: CallVisualizer.ScreenSharingView.Props = .mock() - ) -> CallVisualizer.ScreenSharingViewController.Props { - return .init(screenSharingViewProps: screenShareViewProps) - } -} - -#endif diff --git a/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingViewStyle+Mock.swift b/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingViewStyle+Mock.swift index 98fabed61..731cc44ae 100644 --- a/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingViewStyle+Mock.swift +++ b/GliaWidgetsTests/CallVisualizer/ScreenSharing/Mocks/ScreenSharingViewStyle+Mock.swift @@ -4,14 +4,14 @@ import UIKit extension ScreenSharingViewStyle { static func mock( - title: String = "", + title: String = "Screen Sharing", messageTextFont: UIFont = .systemFont(ofSize: 20, weight: .medium), buttonTitleFont: UIFont = .systemFont(ofSize: 16, weight: .regular) ) -> ScreenSharingViewStyle { return ScreenSharingViewStyle( title: title, header: .mock(), - messageText: L10n.CallVisualizer.ScreenSharing.message, + messageText: Localization.CallVisualizer.ScreenSharing.message, messageTextFont: messageTextFont, messageTextColor: Color.baseDark, buttonStyle: .mock(titleFont: buttonTitleFont), diff --git a/GliaWidgetsTests/CallVisualizer/ScreenSharing/ScreenSharingViewModelTests.swift b/GliaWidgetsTests/CallVisualizer/ScreenSharing/ScreenSharingViewModelTests.swift index 2ad63a3c2..772f204e4 100644 --- a/GliaWidgetsTests/CallVisualizer/ScreenSharing/ScreenSharingViewModelTests.swift +++ b/GliaWidgetsTests/CallVisualizer/ScreenSharing/ScreenSharingViewModelTests.swift @@ -3,12 +3,11 @@ import XCTest final class ScreenSharingViewModelTests: XCTestCase { - private var viewModel: CallVisualizer.ScreenSharingViewModel! + private var model: CallVisualizer.ScreenSharingView.Model! override func tearDownWithError() throws { try super.tearDownWithError() - - viewModel = nil + model = nil } func test_end_screen_sharing() throws { @@ -18,15 +17,9 @@ final class ScreenSharingViewModelTests: XCTestCase { isRunning = false } - viewModel = CallVisualizer.ScreenSharingViewModel( - style: ScreenSharingViewStyle.mock(), - environment: .init(screenShareHandler: screenShareHandlerMock) - ) - - let props = viewModel.props() - + model = .mock(screenSharingHandler: screenShareHandlerMock) XCTAssertTrue(isRunning) - props.screenSharingViewProps.endScreenSharing.tap.execute() + model.event(.endScreenShareTapped) XCTAssertFalse(isRunning) } @@ -34,20 +27,14 @@ final class ScreenSharingViewModelTests: XCTestCase { enum Call { case close } var calls: [Call] = [] - viewModel = CallVisualizer.ScreenSharingViewModel( - style: .mock(), - environment: .init(screenShareHandler: ScreenShareHandler.mock) - ) - viewModel.delegate = Command { event in + model = .mock() + model.delegate = Command { event in switch event { - case .close: + case .closeTapped: calls.append(.close) } } - let props = viewModel.props() - - props.screenSharingViewProps.endScreenSharing.tap.execute() - props.screenSharingViewProps.header.backButton?.tap() + model.event(.closeTapped) XCTAssertEqual(calls, [.close]) } } diff --git a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/CallButtonBarStyle.Mock.swift b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/CallButtonBarStyle.Mock.swift index d94292f77..d9119690a 100644 --- a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/CallButtonBarStyle.Mock.swift +++ b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/CallButtonBarStyle.Mock.swift @@ -43,12 +43,12 @@ extension CallButtonStyle.StateStyle { backgroundColor: ColorType = .fill(color: UIColor.white.withAlphaComponent(0.9)), image: UIImage = Asset.callChat.image, imageColor: ColorType = .fill(color: Color.baseDark), - title: String = L10n.Call.Buttons.Chat.title, + title: String = Localization.Engagement.Chat.title, titleFont: UIFont = .systemFont(ofSize: 12, weight: .regular), titleColor: UIColor = Color.baseLight, textStyle: UIFont.TextStyle = .caption1, accessibility: Accessibility = .init( - label: L10n.Call.Accessibility.Buttons.Chat.Active.label + label: Localization.General.selected ) ) -> CallButtonStyle.StateStyle { return .init( @@ -67,13 +67,11 @@ extension CallButtonStyle.StateStyle { backgroundColor: ColorType = .fill(color: UIColor.black.withAlphaComponent(0.4)), image: UIImage = Asset.callChat.image, imageColor: ColorType = .fill(color: Color.baseLight), - title: String = L10n.Call.Buttons.Chat.title, + title: String = Localization.Engagement.Chat.title, titleFont: UIFont = .systemFont(ofSize: 12, weight: .regular), titleColor: UIColor = Color.baseLight, textStyle: UIFont.TextStyle = .caption1, - accessibility: Accessibility = .init( - label: L10n.Call.Accessibility.Buttons.Chat.Inactive.label - ) + accessibility: Accessibility = .init(label: "") ) -> CallButtonStyle.StateStyle { return .init( backgroundColor: backgroundColor, @@ -91,13 +89,11 @@ extension CallButtonStyle.StateStyle { backgroundColor: ColorType = .fill(color: UIColor.black.withAlphaComponent(0.4)), image: UIImage = Asset.callChat.image, imageColor: ColorType = .fill(color: Color.baseLight), - title: String = L10n.Call.Buttons.Chat.title, + title: String = Localization.Engagement.Chat.title, titleFont: UIFont = .systemFont(ofSize: 12, weight: .regular), titleColor: UIColor = Color.baseLight, textStyle: UIFont.TextStyle = .caption1, - accessibility: Accessibility = .init( - label: L10n.Call.Accessibility.Buttons.Chat.Inactive.label - ) + accessibility: Accessibility = .init(label: "") ) -> CallButtonStyle.StateStyle { return .init( backgroundColor: backgroundColor, diff --git a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/HeaderStyle.Mock.swift b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/HeaderStyle.Mock.swift index b16e5e1c4..65a5cdb9e 100644 --- a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/HeaderStyle.Mock.swift +++ b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/HeaderStyle.Mock.swift @@ -26,16 +26,17 @@ extension HeaderStyle { extension HeaderButtonStyle { static func mock( - image: UIImage = .mock, - color: UIColor = .white + image: UIImage = Asset.back.image, + color: UIColor = .white, + accessibility: Accessibility = .init(label: "", hint: "") ) -> HeaderButtonStyle { - return .init(image: image, color: color) + return .init(image: image, color: color, accessibility: accessibility) } } extension ActionButtonStyle { static func mock( - title: String = L10n.CallVisualizer.ScreenSharing.Button.title, + title: String = Localization.ScreenSharing.VisitorScreen.End.title, titleFont: UIFont = .systemFont(ofSize: 16, weight: .regular), titleColor: UIColor = .white, backgroundColor: ColorType = .fill(color: Color.systemNegative), diff --git a/GliaWidgetsTests/CallVisualizer/VisitorCodeTests.swift b/GliaWidgetsTests/CallVisualizer/VisitorCodeTests.swift index 201650983..d4433cee8 100644 --- a/GliaWidgetsTests/CallVisualizer/VisitorCodeTests.swift +++ b/GliaWidgetsTests/CallVisualizer/VisitorCodeTests.swift @@ -19,21 +19,21 @@ class VisitorCodeTests: XCTestCase { let view = CallVisualizer.VisitorCodeView() view.props = .init(viewState: .success(visitorCode: visitorCode)) XCTAssertEqual(visitorCode, view.renderedVisitorCode, "Visitor Code not displayed properly") - XCTAssertEqual(view.titleLabel.text, L10n.CallVisualizer.VisitorCode.Title.standard) + XCTAssertEqual(view.titleLabel.text, Localization.CallVisualizer.VisitorCode.title) } func test_error_displayed() { let view = CallVisualizer.VisitorCodeView() view.props = .init(viewState: .error(refreshTap: .nop)) XCTAssertTrue(view.stackView.arrangedSubviews.contains(view.refreshButton)) - XCTAssertEqual(view.titleLabel.text, L10n.CallVisualizer.VisitorCode.Title.error) + XCTAssertEqual(view.titleLabel.text, Localization.VisitorCode.failed) } func test_spinner_displayed() { let view = CallVisualizer.VisitorCodeView() view.props = .init(viewState: .loading) XCTAssertTrue(view.stackView.arrangedSubviews.contains(view.spinnerView)) - XCTAssertEqual(view.titleLabel.text, L10n.CallVisualizer.VisitorCode.Title.standard) + XCTAssertEqual(view.titleLabel.text, Localization.CallVisualizer.VisitorCode.title) } func test_closeButton_visibility() { diff --git a/GliaWidgetsTests/Coordinator/RootCoordinator.Environment.Failing.swift b/GliaWidgetsTests/Coordinator/RootCoordinator.Environment.Failing.swift index b339631f5..366c4165a 100644 --- a/GliaWidgetsTests/Coordinator/RootCoordinator.Environment.Failing.swift +++ b/GliaWidgetsTests/Coordinator/RootCoordinator.Environment.Failing.swift @@ -85,6 +85,7 @@ extension EngagementCoordinator.Environment { createSendMessagePayload: { _, _ in fail("\(Self.self).createSendMessagePayload") return .mock() - } + }, + orientationManager: .mock() ) } diff --git a/GliaWidgetsTests/CoreSDKClient.Failing.swift b/GliaWidgetsTests/CoreSDKClient.Failing.swift index 9b2c7a6f8..b8f342fa4 100644 --- a/GliaWidgetsTests/CoreSDKClient.Failing.swift +++ b/GliaWidgetsTests/CoreSDKClient.Failing.swift @@ -5,11 +5,12 @@ extension CoreSdkClient { pushNotifications: .failing, createAppDelegate: { .failing }, clearSession: { fail("\(Self.self).clearSession") }, + localeProvider: .failing, fetchVisitorInfo: { _ in fail("\(Self.self).fetchVisitorInfo") }, updateVisitorInfo: { _, _ in fail("\(Self.self).updateVisitorInfo") }, configureWithConfiguration: { _, _ in fail("\(Self.self).configureWithConfiguration") }, configureWithInteractor: { _ in fail("\(Self.self).configureWithInteractor") }, - listQueues: {_ in fail("\(Self.self).listQueues") }, + listQueues: { _ in fail("\(Self.self).listQueues") }, queueForEngagement: { _, _ in fail("\(Self.self).queueForEngagement") }, requestMediaUpgradeWithOffer: { _, _ in fail("\(Self.self).requestMediaUpgradeWithOffer") }, sendMessagePreview: { _, _ in fail("\(Self.self).sendMessagePreview") }, @@ -89,3 +90,12 @@ extension CoreSdkClient.AppDelegate { } ) } + +extension CoreSdkClient.LocaleProvider { + static let failing = Self( + getRemoteString: { _ in + fail("\(Self.self).getRemoteString") + return nil + } + ) +} diff --git a/GliaWidgetsTests/FoundationBased.Failing.swift b/GliaWidgetsTests/FoundationBased.Failing.swift index c3254f1bb..6dfa53e0a 100644 --- a/GliaWidgetsTests/FoundationBased.Failing.swift +++ b/GliaWidgetsTests/FoundationBased.Failing.swift @@ -1,4 +1,5 @@ @testable import GliaWidgets +import Combine extension FoundationBased.FileManager { static let failing = Self( @@ -89,6 +90,10 @@ extension FoundationBased.NotificationCenter { }, removeObserverWithNameAndObject: {_, _, _ in fail("\(Self.self).removeObserverWithNameAndObject") + }, + publisherForNotification: { _ in + fail("\(Self.self).publisherForNotification") + return Empty().eraseToAnyPublisher() } ) } diff --git a/GliaWidgetsTests/Glia.Environment.Failing.swift b/GliaWidgetsTests/Glia.Environment.Failing.swift index fe3856b3d..14ba7bcff 100644 --- a/GliaWidgetsTests/Glia.Environment.Failing.swift +++ b/GliaWidgetsTests/Glia.Environment.Failing.swift @@ -54,7 +54,8 @@ extension Glia.Environment { return .mock() }, screenShareHandler: .mock, - messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock + messagesWithUnreadCountLoaderScheduler: CoreSdkClient.reactiveSwiftDateSchedulerMock, + orientationManager: .mock() ) } diff --git a/GliaWidgetsTests/Info.plist b/GliaWidgetsTests/Info.plist index 735dbb5ea..c89aab65f 100644 --- a/GliaWidgetsTests/Info.plist +++ b/GliaWidgetsTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.1.0 + 2.2.0 CFBundleVersion 1 diff --git a/GliaWidgetsTests/Resources/LocalizationTests.swift b/GliaWidgetsTests/Resources/LocalizationTests.swift new file mode 100644 index 000000000..14a08f0c2 --- /dev/null +++ b/GliaWidgetsTests/Resources/LocalizationTests.swift @@ -0,0 +1,66 @@ +import Foundation +import XCTest +@testable import GliaWidgets + +final class LocalizationTests: XCTestCase { + let testString = "Glia" + + func test_stringProvider() { + let stringProviding = StringProviding(getRemoteString: { _ in self.testString }) + + let localizationString = Localization.tr( + "", + "", + fallback: "", + stringProviding: stringProviding + ) + + XCTAssertEqual(localizationString, testString) + } + + func test_fallback() { + let localizationString = Localization.tr( + "", + "", + fallback: testString + ) + + XCTAssertEqual(localizationString, testString) + } + + func test_fallbackWhenStringProvidingReturnsNil() { + let stringProviding = StringProviding(getRemoteString: { _ in nil }) + + let localizationString = Localization.tr( + "", + "", + fallback: testString, + stringProviding: stringProviding + ) + + XCTAssertEqual(localizationString, testString) + } + + func test_fileFromString() { + let localizationString = Localization.tr( + "Localizable", + "alert.action.settings", + fallback: "" + ) + + XCTAssertEqual(localizationString, "Settings") + } + + func test_fileFromStringWhenStringProvidingReturnsNil() { + let stringProviding = StringProviding(getRemoteString: { _ in nil }) + + let localizationString = Localization.tr( + "Localizable", + "alert.action.settings", + fallback: "", + stringProviding: stringProviding + ) + + XCTAssertEqual(localizationString, "Settings") + } +} diff --git a/GliaWidgetsTests/SecureConversations/ChatTranscript/TranscriptModelTests.swift b/GliaWidgetsTests/SecureConversations/ChatTranscript/TranscriptModelTests.swift index 67c11a36a..74ad9ad53 100644 --- a/GliaWidgetsTests/SecureConversations/ChatTranscript/TranscriptModelTests.swift +++ b/GliaWidgetsTests/SecureConversations/ChatTranscript/TranscriptModelTests.swift @@ -284,7 +284,6 @@ final class SecureConversationsTranscriptModelTests: XCTestCase { XCTAssertTrue(viewModel.validateMessage()) } - func testSendMessageUsesSecureEndpoint() { var modelEnv = TranscriptModel.Environment.failing let fileUploadListModel = FileUploadListViewModel.mock() @@ -379,7 +378,7 @@ final class SecureConversationsTranscriptModelTests: XCTestCase { callback(.success(1)) } modelEnv.startSocketObservation = {} - modelEnv.gcd.mainQueue.asyncAfterDeadline = { _, callback in } + modelEnv.gcd.mainQueue.asyncAfterDeadline = { _, _ in } modelEnv.loadChatMessagesFromHistory = { true } let scheduler = CoreSdkClient.ReactiveSwift.TestScheduler() modelEnv.messagesWithUnreadCountLoaderScheduler = scheduler @@ -451,7 +450,7 @@ final class SecureConversationsTranscriptModelTests: XCTestCase { alertConfiguration: .mock() ) - modelEnv.fetchChatHistory = { callback in + modelEnv.fetchChatHistory = { _ in let uuid = UUID.mock.uuidString let message = CoreSdkClient.Message( id: uuid, @@ -491,7 +490,7 @@ final class SecureConversationsTranscriptModelTests: XCTestCase { callback(.success(0)) } modelEnv.startSocketObservation = {} - modelEnv.gcd.mainQueue.asyncAfterDeadline = { _, callback in } + modelEnv.gcd.mainQueue.asyncAfterDeadline = { _, _ in } modelEnv.loadChatMessagesFromHistory = { true } let scheduler = CoreSdkClient.ReactiveSwift.TestScheduler() modelEnv.messagesWithUnreadCountLoaderScheduler = scheduler @@ -515,7 +514,7 @@ final class SecureConversationsTranscriptModelTests: XCTestCase { alertConfiguration: .mock() ) - modelEnv.fetchChatHistory = { callback in + modelEnv.fetchChatHistory = { _ in let uuid = UUID.mock.uuidString let message = CoreSdkClient.Message( id: uuid, @@ -541,4 +540,126 @@ final class SecureConversationsTranscriptModelTests: XCTestCase { viewModel.start() scheduler.run() } + + func testIsSecureConversationsAvailableIsFalseIsDueToEmptyQueue() { + var modelEnvironment = TranscriptModel.Environment.failing + modelEnvironment.fileManager = .mock + modelEnvironment.createFileUploadListModel = { + .mock(environment: $0) + } + var availabilityEnv = SecureConversations.Availability.Environment.failing + availabilityEnv.listQueues = { callback in + callback([], nil) + } + availabilityEnv.isAuthenticated = { true } + let model = TranscriptModel( + isCustomCardSupported: false, + environment: modelEnvironment, + availability: .init(environment: availabilityEnv), + deliveredStatusText: "", + interactor: .failing, + alertConfiguration: .mock() + ) + XCTAssertFalse(model.isSecureConversationsAvailable) + } + + func testIsSecureConversationsAvailableIsFalseDueToUnauthenticated() { + var modelEnvironment = TranscriptModel.Environment.failing + modelEnvironment.fileManager = .mock + modelEnvironment.createFileUploadListModel = { + .mock(environment: $0) + } + var availabilityEnv = SecureConversations.Availability.Environment.failing + availabilityEnv.listQueues = { callback in + callback([.mock()], nil) + } + availabilityEnv.isAuthenticated = { false } + let model = TranscriptModel( + isCustomCardSupported: false, + environment: modelEnvironment, + availability: .init(environment: availabilityEnv), + deliveredStatusText: "", + interactor: .failing, + alertConfiguration: .mock() + ) + XCTAssertFalse(model.isSecureConversationsAvailable) + } + + func testIsSecureConversationsAvailableIsFalseDueToListQueuesError() { + var modelEnvironment = TranscriptModel.Environment.failing + modelEnvironment.fileManager = .mock + modelEnvironment.createFileUploadListModel = { + .mock(environment: $0) + } + var availabilityEnv = SecureConversations.Availability.Environment.failing + availabilityEnv.listQueues = { callback in + callback(nil, .mock()) + } + availabilityEnv.isAuthenticated = { false } + let model = TranscriptModel( + isCustomCardSupported: false, + environment: modelEnvironment, + availability: .init(environment: availabilityEnv), + deliveredStatusText: "", + interactor: .failing, + alertConfiguration: .mock() + ) + XCTAssertFalse(model.isSecureConversationsAvailable) + } + + func testIsSecureConversationsAvailableIsTrue() { + var modelEnvironment = TranscriptModel.Environment.failing + modelEnvironment.fileManager = .mock + modelEnvironment.createFileUploadListModel = { + .mock(environment: $0) + } + var availabilityEnv = SecureConversations.Availability.Environment.failing + availabilityEnv.listQueues = { callback in + callback([.mock()], nil) + } + availabilityEnv.isAuthenticated = { true } + let model = TranscriptModel( + isCustomCardSupported: false, + environment: modelEnvironment, + availability: .init(environment: availabilityEnv), + deliveredStatusText: "", + interactor: .failing, + alertConfiguration: .mock() + ) + XCTAssertFalse(model.isSecureConversationsAvailable) + } + + func testSetIsSecureConversationsAvailableCallsAction() throws { + var modelEnvironment = TranscriptModel.Environment.failing + modelEnvironment.fileManager = .mock + modelEnvironment.createFileUploadListModel = { + .mock(environment: $0) + } + var availabilityEnv = SecureConversations.Availability.Environment.failing + availabilityEnv.listQueues = { callback in + callback([.mock()], nil) + } + availabilityEnv.isAuthenticated = { true } + let model = TranscriptModel( + isCustomCardSupported: false, + environment: modelEnvironment, + availability: .init(environment: availabilityEnv), + deliveredStatusText: "", + interactor: .failing, + alertConfiguration: .mock() + ) + var actions: [TranscriptModel.Action] = [] + model.action = { + actions.append($0) + } + model.setIsSecureConversationsAvailable(true) + XCTAssertEqual(actions.count, 1) + let receivedAction = try XCTUnwrap(actions.first) + switch receivedAction { + case .transcript(.messageCenterAvailabilityUpdated): + break + default: + XCTFail("Unexpected action: \(receivedAction)") + } + } } diff --git a/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.Mock.swift b/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.Mock.swift index 383ed28f0..85b8203a4 100644 --- a/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.Mock.swift +++ b/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.Mock.swift @@ -1,14 +1,73 @@ -import Foundation +import UIKit @testable import GliaWidgets -extension SecureConversations.ConfirmationViewModel { - static let mock = SecureConversations.ConfirmationViewModel( - environment: .mock - ) +extension SecureConversations.ConfirmationStyle { + static func mock( + title: String = "", + titleStyle: TitleStyle = .mock(), + subtitleStyle: SubtitleStyle = .mock(), + checkMessagesButtonStyle: CheckMessagesButtonStyle = .mock() + ) -> SecureConversations.ConfirmationStyle { + .init( + header: .mock(), + headerTitle: title, + confirmationImage: .mock, + confirmationImageTint: Color.baseLight, + titleStyle: titleStyle, + subtitleStyle: subtitleStyle, + checkMessagesButtonStyle: checkMessagesButtonStyle, + backgroundColor: Color.baseLight + ) + } } -extension SecureConversations.ConfirmationViewModel.Environment { - static let mock = SecureConversations.ConfirmationViewModel.Environment( - confirmationStyle: Theme().secureConversationsConfirmation - ) +extension SecureConversations.ConfirmationStyle.TitleStyle { + static func mock( + text: String = "Title label", + font: UIFont = ThemeFont().header1, + color: UIColor = Color.baseDark, + accessibility: Accessibility = .unsupported + ) -> SecureConversations.ConfirmationStyle.TitleStyle { + return .init( + text: text, + font: font, + color: color, + accessibility: accessibility + ) + } } + +extension SecureConversations.ConfirmationStyle.SubtitleStyle { + static func mock( + text: String = "Title label", + font: UIFont = ThemeFont().header1, + color: UIColor = Color.baseDark, + accessibility: Accessibility = .unsupported + ) -> SecureConversations.ConfirmationStyle.SubtitleStyle { + return .init( + text: text, + font: font, + color: color, + accessibility: accessibility + ) + } +} + +extension SecureConversations.ConfirmationStyle.CheckMessagesButtonStyle { + static func mock( + title: String = "Title label", + font: UIFont = ThemeFont().header1, + textColor: UIColor = Color.baseDark, + backgroundColor: UIColor = Color.baseLight, + accessibility: Accessibility = .unsupported + ) -> SecureConversations.ConfirmationStyle.CheckMessagesButtonStyle { + return .init( + title: title, + font: font, + textColor: textColor, + backgroundColor: backgroundColor, + accessibility: accessibility + ) + } +} + diff --git a/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModelTests.swift b/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModelTests.swift index ddf345042..a1d6cc5d5 100644 --- a/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModelTests.swift +++ b/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModelTests.swift @@ -3,37 +3,35 @@ import XCTest @testable import GliaWidgets final class SecureConversationsConfirmationViewModelTests: XCTestCase { - typealias ConfirmationViewModel = SecureConversations.ConfirmationViewModel - var viewModel: ConfirmationViewModel = .mock - - override func setUp() { - viewModel = .mock - } + typealias ConfirmationViewModel = SecureConversations.ConfirmationViewSwiftUI.Model + + var viewModel: ConfirmationViewModel = .init( + environment: .init( + orientationManager: .mock(), uiApplication: .mock + ), + style: Theme().defaultSecureConversationsConfirmationStyle, + delegate: nil + ) } // Props extension SecureConversationsConfirmationViewModelTests { func testPropsDoNotGenerateABackButton() { - let props = viewModel.props().confirmationViewProps.header + let backButton = viewModel.style.header.backButton - XCTAssertNil(props.backButton) + XCTAssertNil(backButton) } func testPropsGenerateCorrectTitle() { let title = "Test" - var style = Theme().secureConversationsConfirmation - style.headerTitle = title - - viewModel.environment = .init(confirmationStyle: style) - - let props = viewModel.props() - XCTAssertEqual(props.confirmationViewProps.header.title, title) - } - - func testPropsGenerateEndButtonWithAccessibilityIdentifier() { - let props = viewModel.props().confirmationViewProps.header.endButton - - XCTAssertEqual(props.accessibilityIdentifier, "header_end_button") + viewModel = .init( + environment: .init( + orientationManager: .mock(), uiApplication: .mock + ), + style: .mock(title: title), + delegate: nil + ) + XCTAssertEqual(viewModel.style.headerTitle, title) } } @@ -58,11 +56,16 @@ extension SecureConversationsConfirmationViewModelTests { func testPressingCloseButtonCallsDelegate() throws { var receivedEvent: ConfirmationViewModel.DelegateEvent? - viewModel.delegate = { event in - receivedEvent = event - } - - viewModel.props().confirmationViewProps.header.closeButton.tap() + viewModel = .init( + environment: .init( + orientationManager: .mock(), uiApplication: .mock + ), + style: .mock(), + delegate: { event in + receivedEvent = event + } + ) + viewModel.delegate?(.closeTapped) switch try XCTUnwrap(receivedEvent) { case .closeTapped: @@ -74,11 +77,16 @@ extension SecureConversationsConfirmationViewModelTests { func testPressingCheckMessagesButtonCallsDelegate() throws { var receivedEvent: ConfirmationViewModel.DelegateEvent? - viewModel.delegate = { event in - receivedEvent = event - } - - viewModel.props().confirmationViewProps.checkMessageButtonTap() + viewModel = .init( + environment: .init( + orientationManager: .mock(), uiApplication: .mock + ), + style: .mock(), + delegate: { event in + receivedEvent = event + } + ) + viewModel.delegate?(.chatTranscriptScreenRequested) switch try XCTUnwrap(receivedEvent) { case .chatTranscriptScreenRequested: @@ -86,20 +94,4 @@ extension SecureConversationsConfirmationViewModelTests { default: XCTFail() } } - - func testReportingAChangeRendersProps() throws { - var receivedEvent: ConfirmationViewModel.DelegateEvent? - - viewModel.delegate = { event in - receivedEvent = event - } - - viewModel.reportChange() - - switch try XCTUnwrap(receivedEvent) { - case .renderProps(_): - XCTAssertTrue(true) - default: XCTFail() - } - } } diff --git a/GliaWidgetsTests/Sources/CallViewModelTests.swift b/GliaWidgetsTests/Sources/CallViewModelTests.swift index b3500afff..68b97d7e4 100644 --- a/GliaWidgetsTests/Sources/CallViewModelTests.swift +++ b/GliaWidgetsTests/Sources/CallViewModelTests.swift @@ -335,4 +335,16 @@ class CallViewModelTests: XCTestCase { XCTAssertEqual(call.kind.value, .audio) } + + func test_startMethodDoesNotHandleInteractorStateEnded() { + let interactor: Interactor = .mock() + interactor.state = .ended(.byOperator) + let call: Call = .mock() + let viewModel: CallViewModel = .mock(interactor: interactor, call: call) + + XCTAssertEqual(call.state.value, .none) + viewModel.start() + + XCTAssertEqual(call.state.value, .none) + } } diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift new file mode 100644 index 000000000..008911f97 --- /dev/null +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests+Transferring.swift @@ -0,0 +1,79 @@ +@testable import GliaWidgets +import GliaCoreSDK +import XCTest + +extension ChatViewModelTests { + func test_mediaButtonVisibilityDuringTransferring() throws { + enum Call: Equatable { + enum Visibility { case enabled, disabled } + case updateVisibility(Visibility) + } + + var calls: [Call] = [] + + var interactorEnv = Interactor.Environment.failing + // To ensure `sendMessageWithMessagePayload` is not called in case of Postback Button + interactorEnv.coreSdk.sendMessageWithMessagePayload = { _, _ in + XCTFail("createSendMessagePayload should not be called") + } + interactorEnv.gcd.mainQueue.asyncIfNeeded = { $0() } + 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 } + env.fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in } + env.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] } + env.createFileUploadListModel = { _ in .mock() } + env.createSendMessagePayload = { _, _ in .mock() } + let site: CoreSdkClient.Site = try .mock( + allowedFileSenders: .init(operator: true, visitor: true) + ) + env.fetchSiteConfigurations = { completion in + completion(.success(site)) + } + env.getCurrentEngagement = { .mock() } + viewModel = .mock(interactor: interactorMock, environment: env) + + viewModel.action = { action in + switch action { + case let .setAttachmentButtonVisibility(visibility): + switch visibility { + case .enabled: + calls.append(.updateVisibility(.enabled)) + case .disabled: + calls.append(.updateVisibility(.disabled)) + } + default: + break + } + } + interactorMock.state = .engaged(nil) + XCTAssertEqual( + calls, + [.updateVisibility(.enabled)] + ) + + interactorMock.notify(.engagementTransferring) + XCTAssertEqual( + calls, + [ + .updateVisibility(.enabled), + .updateVisibility(.disabled) + ] + ) + + interactorMock.notify(.engagementTransferred(GliaCoreSDK.Operator.mock())) + + XCTAssertEqual( + calls, + [ + .updateVisibility(.enabled), + .updateVisibility(.disabled), + .updateVisibility(.enabled) + ] + ) + } +} diff --git a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift index 01df8c1e5..64593b0a2 100644 --- a/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift +++ b/GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift @@ -701,6 +701,31 @@ class ChatViewModelTests: XCTestCase { XCTAssertEqual(viewModel.receivedMessageIds, [expectedMessageId.uppercased()]) XCTAssertEqual(try XCTUnwrap(receivedMessage).id, expectedMessageId) } + + func test_messageContainedInPendingSectionDiscardsOneDeliveredViaSocket() throws { + var viewModelEnv = ChatViewModel.Environment.failing() + viewModelEnv.createFileUploadListModel = { _ in .mock() } + viewModelEnv.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] } + viewModelEnv.fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in } + viewModelEnv.loadChatMessagesFromHistory = { true } + viewModelEnv.fetchSiteConfigurations = { _ in } + viewModelEnv.fetchChatHistory = { callback in + callback(.success([])) + } + let viewModel: ChatViewModel = .mock(environment: viewModelEnv) + viewModel.isViewLoaded = true + viewModel.start() + let messageIdSuffix = "1D_123" + + let outgoingMessage = OutgoingMessage(payload: .mock(messageIdSuffix: messageIdSuffix)) + viewModel.pendingSection.append( + .init(kind: .outgoingMessage(outgoingMessage)) + ) + + viewModel.interactorEvent(.receivedMessage(.mock(id: outgoingMessage.payload.messageId.rawValue))) + XCTAssertEqual(viewModel.messagesSection.items.count, 0) + XCTAssertEqual(viewModel.receivedMessageIds, []) + } } extension ChatChoiceCardOption { diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift index 5410d4d5c..df6064d91 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests+StartEngagement.swift @@ -4,6 +4,10 @@ import XCTest @testable import GliaWidgets extension GliaTests { + override class func tearDown() { + Glia.sharedInstance.stringProviding = nil + } + func testStartEngagementThrowsErrorWhenEngagementAlreadyExists() throws { let sdk = Glia(environment: .failing) sdk.rootCoordinator = .mock(engagementKind: .chat, screenShareHandler: .mock) @@ -51,4 +55,210 @@ extension GliaTests { XCTAssertTrue(interactor.isConfigurationPerformed) XCTAssertEqual(calls, [.configureWithInteractor, .configureWithConfiguration]) } + + func testCompanyNameIsReceivedFromTheme() throws { + var environment = Glia.Environment.failing + var resultingViewFactory: ViewFactory? + + environment.createRootCoordinator = { _, viewFactory, _, _, _, _, _ in + resultingViewFactory = viewFactory + + return .mock( + interactor: .mock(environment: .failing), + viewFactory: viewFactory, + sceneProvider: nil, + engagementKind: .none, + screenShareHandler: .mock, + features: [], + environment: .failing + ) + } + + let sdk = Glia(environment: environment) + + let theme = Theme() + 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) + + let configuredSdkTheme = resultingViewFactory?.theme + XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Glia 1") + XCTAssertEqual(configuredSdkTheme?.chat.connect.queue.firstText, "Glia 2") + } + + func testCompanyNameIsReceivedFromRemoteStrings() throws { + var environment = Glia.Environment.failing + var resultingViewFactory: ViewFactory? + + environment.createRootCoordinator = { _, viewFactory, _, _, _, _, _ in + resultingViewFactory = viewFactory + + return .mock( + interactor: .mock(environment: .failing), + viewFactory: viewFactory, + sceneProvider: nil, + engagementKind: .none, + screenShareHandler: .mock, + features: [], + environment: .failing + ) + } + + environment.coreSdk.localeProvider.getRemoteString = { _ in "Glia" } + environment.coreSdk.configureWithInteractor = { _ in } + environment.coreSdk.configureWithConfiguration = { _, completion in + completion?() + } + environment.coreSdk.getCurrentEngagement = { nil } + + let sdk = Glia(environment: environment) + + // Even if theme is set, the remote string takes priority. + let theme = Theme() + 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) + + let configuredSdkTheme = resultingViewFactory?.theme + XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Glia") + XCTAssertEqual(configuredSdkTheme?.chat.connect.queue.firstText, "Glia") + } + + func testCompanyNameIsReceivedFromConfiguration() throws { + var environment = Glia.Environment.failing + var resultingViewFactory: ViewFactory? + + environment.createRootCoordinator = { _, viewFactory, _, _, _, _, _ in + resultingViewFactory = viewFactory + + return .mock( + interactor: .mock(environment: .failing), + viewFactory: viewFactory, + sceneProvider: nil, + engagementKind: .none, + screenShareHandler: .mock, + features: [], + environment: .failing + ) + } + + let sdk = Glia(environment: environment) + + try sdk.configure(with: .mock(companyName: "Glia")) + try sdk.startEngagement(engagementKind: .chat, in: ["queueId"]) + + let configuredSdkTheme = resultingViewFactory?.theme + XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Glia") + XCTAssertEqual(configuredSdkTheme?.chat.connect.queue.firstText, "Glia") + } + + func testCompanyNameIsReceivedFromLocalStrings() throws { + var environment = Glia.Environment.failing + var resultingViewFactory: ViewFactory? + + environment.createRootCoordinator = { _, viewFactory, _, _, _, _, _ in + resultingViewFactory = viewFactory + + return .mock( + interactor: .mock(environment: .failing), + viewFactory: viewFactory, + sceneProvider: nil, + engagementKind: .none, + screenShareHandler: .mock, + features: [], + environment: .failing + ) + } + + let sdk = Glia(environment: environment) + + try sdk.configure(with: .mock()) + try sdk.startEngagement(engagementKind: .chat, in: ["queueId"]) + + let configuredSdkTheme = resultingViewFactory?.theme + XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Company Name") + XCTAssertEqual(configuredSdkTheme?.chat.connect.queue.firstText, "Company Name") + } + + func testCompanyNameIsReceivedFromThemeIfCustomLocalesIsEmpty() throws { + var environment = Glia.Environment.failing + var resultingViewFactory: ViewFactory? + + environment.createRootCoordinator = { _, viewFactory, _, _, _, _, _ in + resultingViewFactory = viewFactory + + return .mock( + interactor: .mock(environment: .failing), + viewFactory: viewFactory, + sceneProvider: nil, + engagementKind: .none, + screenShareHandler: .mock, + features: [], + environment: .failing + ) + } + + environment.coreSdk.localeProvider.getRemoteString = { _ in "" } + environment.coreSdk.configureWithInteractor = { _ in } + environment.coreSdk.configureWithConfiguration = { _, completion in + completion?() + } + environment.coreSdk.getCurrentEngagement = { nil } + + let sdk = Glia(environment: environment) + + let theme = Theme() + 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) + + let configuredSdkTheme = resultingViewFactory?.theme + XCTAssertEqual(configuredSdkTheme?.call.connect.queue.firstText, "Glia 1") + XCTAssertEqual(configuredSdkTheme?.chat.connect.queue.firstText, "Glia 2") + } + + func testCompanyNameIsReceivedFromLocalFallbackIfCustomLocalesIsEmpty() throws { + var environment = Glia.Environment.failing + var resultingViewFactory: ViewFactory? + + environment.createRootCoordinator = { _, viewFactory, _, _, _, _, _ in + resultingViewFactory = viewFactory + + return .mock( + interactor: .mock(environment: .failing), + viewFactory: viewFactory, + sceneProvider: nil, + engagementKind: .none, + screenShareHandler: .mock, + features: [], + environment: .failing + ) + } + + environment.coreSdk.localeProvider.getRemoteString = { _ in "" } + environment.coreSdk.configureWithInteractor = { _ in } + environment.coreSdk.configureWithConfiguration = { _, completion in + // Simulating what happens in the Widgets when the configuration gets done + Glia.sharedInstance.stringProviding = StringProviding( + getRemoteString: environment.coreSdk.localeProvider.getRemoteString + ) + completion?() + } + 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) + XCTAssertEqual(configuredSdkTheme?.chat.connect.queue.firstText, localFallbackCompanyName) + } } diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests.swift b/GliaWidgetsTests/Sources/Glia/GliaTests.swift index 8fb2e2567..46dcde9ca 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests.swift @@ -49,7 +49,7 @@ final class GliaTests: XCTestCase { gliaEnv.fileManager = fileManager gliaEnv.coreSdk.configureWithInteractor = { _ in } 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 @@ -284,4 +284,3 @@ final class GliaTests: XCTestCase { XCTAssertEqual(delegate.invokedEventCallParameterList, [.maximized]) } } - diff --git a/GliaWidgetsTests/Sources/Survey.SingleChouceQuestionViewTests.swift b/GliaWidgetsTests/Sources/Survey.SingleChouceQuestionViewTests.swift new file mode 100644 index 000000000..3a277f00b --- /dev/null +++ b/GliaWidgetsTests/Sources/Survey.SingleChouceQuestionViewTests.swift @@ -0,0 +1,92 @@ +import Foundation +import XCTest +@testable import GliaWidgets + +final class SurveySingleChoiceQuestionViewTests: XCTestCase { + static let firstOption = Survey.Option(name: "Option 1", value: "1") + static let secondOption = Survey.Option(name: "Option 2", value: "2") + + var props: Survey.SingleChoiceQuestionView.Props = { + var props = Survey.SingleChoiceQuestionView.Props( + id: "1", + title: "Survey", + isRequired: true, + accessibility: .init(value: "") + ) + props.options = [ + firstOption, + secondOption + ] + return props + }() + + func test_regularOptionIsActive() { + let regularOption = Self.firstOption + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: regularOption + ) + + XCTAssertEqual(selection, .active) + } + + func test_defaultOptionIsSelected() { + var hasSelectedDefaultOption = false + + let defaultOption = Survey.Option( + name: "Option 1", + value: "1", + select: { _ in + hasSelectedDefaultOption = true + } + ) + + props.defaultOption = defaultOption + props.options = [defaultOption, Self.secondOption] + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: defaultOption + ) + + XCTAssertEqual(selection, .selected) + XCTAssertEqual(hasSelectedDefaultOption, true) + } + + func test_regularOptionIsActiveWhenDefaultIsPresent() { + let defaultOption = Self.firstOption + props.defaultOption = defaultOption + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: Self.secondOption + ) + + XCTAssertEqual(selection, .active) + } + + func test_selectedOptionIsSelected() { + let selectedOption = Self.firstOption + props.selected = selectedOption + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: selectedOption + ) + + XCTAssertEqual(selection, .selected) + } + + func test_regularOptionIsActiveWhenSelectedIsPresent() { + let selectedOption = Self.firstOption + props.selected = selectedOption + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: Self.secondOption + ) + + XCTAssertEqual(selection, .active) + } +} diff --git a/GliaWidgetsTests/UIKitBased.Failing.swift b/GliaWidgetsTests/UIKitBased.Failing.swift index baf3fa66a..6edfaf70b 100644 --- a/GliaWidgetsTests/UIKitBased.Failing.swift +++ b/GliaWidgetsTests/UIKitBased.Failing.swift @@ -65,6 +65,10 @@ extension UIKitBased.UIDevice { }, isProximityMonitoringEnabled: { _ in fail("\(Self.self).isProximityMonitoringEnabled") + }, + orientationDidChangeNotification: { + fail("\(Self.self).orientationDidChangeNotification") + return NSNotification.Name(rawValue: "") } ) } diff --git a/Package.swift b/Package.swift index ef6d3d39f..eef33d9d9 100644 --- a/Package.swift +++ b/Package.swift @@ -11,10 +11,6 @@ let package = Package( .library( name: "GliaWidgets", targets: ["GliaWidgetsSDK"] - ), - .library( - name: "GliaWidgets-xcframework", - targets: ["GliaWidgetsSDK-xcframework"] ) ], targets: [ @@ -35,13 +31,8 @@ let package = Package( ), .binaryTarget( name: "GliaCoreSDK", - url: "https://github.com/salemove/ios-bundle/releases/download/1.1.2/GliaCoreSDK.xcframework.zip", - checksum: "a4b52dd6b930904ee655d3065a1c76eda2546e2e5cad6649826f61843737e31b" - ), - .binaryTarget( - name: "GliaWidgetsXcf", - url: "https://github.com/salemove/ios-sdk-widgets/releases/download/2.0.6/GliaWidgetsXcf.xcframework.zip", - checksum: "27dd290835cc21e8191d16d128c8b4d19a7bda8f34029821626eefd91a7c448b" + url: "https://github.com/salemove/ios-bundle/releases/download/1.1.5/GliaCoreSDK.xcframework.zip", + checksum: "05ea83d9bdddb122962a04662cf6a79033b84d8910f46a6aab9aae5a5d4be58b" ), .target( name: "GliaWidgets", @@ -64,16 +55,6 @@ let package = Package( "WebRTC", "GliaWidgets" ] - ), - .target( - name: "GliaWidgetsSDK-xcframework", - dependencies: [ - "GliaCoreSDK", - "GliaWidgetsXcf", - "GliaCoreDependency", - "TwilioVoice", - "WebRTC" - ] ) ] ) diff --git a/Podfile.lock b/Podfile.lock index bdcb7953f..cc7acf675 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -7,7 +7,7 @@ PODS: - AccessibilitySnapshot/Core - SnapshotTesting (~> 1.0) - GliaCoreDependency (1.2) - - GliaCoreSDK (1.1.2): + - GliaCoreSDK (1.1.5): - GliaCoreDependency (= 1.2) - TwilioVoice (= 6.3.1) - WebRTC-lib (= 96.0.0) @@ -34,7 +34,7 @@ SPEC REPOS: SPEC CHECKSUMS: AccessibilitySnapshot: a91e4a69f870188b51f43863d9fc7269d07cdd93 GliaCoreDependency: 87b3897f0d85321ecf77f1faa829211ad527e54d - GliaCoreSDK: 4ba45fcff96ae79480cabf3b871c6932ca18efae + GliaCoreSDK: 1cba2761bf4b0205479c7f5cafcc8893444f6c70 SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b SwiftLint: 13280e21cdda6786ad908dc6e416afe5acd1fcb7 TwilioVoice: 098a959181d4607921f5822d3c9f13043ea4075b diff --git a/SnapshotTests/AlertViewControllerDynamicTypeFontTests.swift b/SnapshotTests/AlertViewControllerDynamicTypeFontTests.swift index ab1ab191e..bfad7fd7d 100644 --- a/SnapshotTests/AlertViewControllerDynamicTypeFontTests.swift +++ b/SnapshotTests/AlertViewControllerDynamicTypeFontTests.swift @@ -10,11 +10,8 @@ final class AlertViewControllerDynamicTypeFontTests: SnapshotTestCase { accepted: {}, declined: {} )) - assertSnapshot( - matching: alert, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + alert.assertSnapshot(as: .extra3LargeFont, in: .portrait) + alert.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_mediaUpgradeOffer_extra3Large() { @@ -23,11 +20,8 @@ final class AlertViewControllerDynamicTypeFontTests: SnapshotTestCase { accepted: {}, declined: {} )) - assertSnapshot( - matching: alert, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + alert.assertSnapshot(as: .extra3LargeFont, in: .portrait) + alert.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_messageAlert_extra3Large() { @@ -36,11 +30,8 @@ final class AlertViewControllerDynamicTypeFontTests: SnapshotTestCase { accessibilityIdentifier: nil, dismissed: {} )) - assertSnapshot( - matching: alert, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + alert.assertSnapshot(as: .extra3LargeFont, in: .portrait) + alert.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_singleAction_extra3Large() { @@ -49,11 +40,8 @@ final class AlertViewControllerDynamicTypeFontTests: SnapshotTestCase { accessibilityIdentifier: "mocked-accessibility-identifier", actionTapped: {} )) - assertSnapshot( - matching: alert, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + alert.assertSnapshot(as: .extra3LargeFont, in: .portrait) + alert.assertSnapshot(as: .extra3LargeFont, in: .landscape) } private func alert(ofKind kind: AlertViewController.Kind) -> AlertViewController { @@ -61,7 +49,6 @@ final class AlertViewControllerDynamicTypeFontTests: SnapshotTestCase { kind: kind, viewFactory: .mock() ) - viewController.view.frame = UIScreen.main.bounds return viewController } } diff --git a/SnapshotTests/AlertViewControllerLayoutTests.swift b/SnapshotTests/AlertViewControllerLayoutTests.swift index df8c9861d..683507736 100644 --- a/SnapshotTests/AlertViewControllerLayoutTests.swift +++ b/SnapshotTests/AlertViewControllerLayoutTests.swift @@ -2,18 +2,15 @@ import SnapshotTesting import XCTest -class AlertViewControllerLayoutTests: SnapshotTestCase { +final class AlertViewControllerLayoutTests: SnapshotTestCase { func test_screenSharingOffer() { let alert = alert(ofKind: .screenShareOffer( .mock(), accepted: {}, declined: {} )) - assertSnapshot( - matching: alert, - as: .image, - named: nameForDevice() - ) + alert.assertSnapshot(as: .image, in: .portrait) + alert.assertSnapshot(as: .image, in: .landscape) } func test_mediaUpgradeOffer() { @@ -22,11 +19,8 @@ class AlertViewControllerLayoutTests: SnapshotTestCase { accepted: {}, declined: {} )) - assertSnapshot( - matching: alert, - as: .image, - named: nameForDevice() - ) + alert.assertSnapshot(as: .image, in: .portrait) + alert.assertSnapshot(as: .image, in: .landscape) } func test_messageAlert() { @@ -35,11 +29,8 @@ class AlertViewControllerLayoutTests: SnapshotTestCase { accessibilityIdentifier: nil, dismissed: {} )) - assertSnapshot( - matching: alert, - as: .image, - named: nameForDevice() - ) + alert.assertSnapshot(as: .image, in: .portrait) + alert.assertSnapshot(as: .image, in: .landscape) } func test_singleAction() { @@ -48,11 +39,8 @@ class AlertViewControllerLayoutTests: SnapshotTestCase { accessibilityIdentifier: "mocked-accessibility-identifier", actionTapped: {} )) - assertSnapshot( - matching: alert, - as: .image, - named: nameForDevice() - ) + alert.assertSnapshot(as: .image, in: .portrait) + alert.assertSnapshot(as: .image, in: .landscape) } private func alert(ofKind kind: AlertViewController.Kind) -> AlertViewController { @@ -60,7 +48,6 @@ class AlertViewControllerLayoutTests: SnapshotTestCase { kind: kind, viewFactory: .mock() ) - viewController.view.frame = UIScreen.main.bounds return viewController } } diff --git a/SnapshotTests/AlertViewControllerTests.swift b/SnapshotTests/AlertViewControllerVoiceOverTests.swift similarity index 58% rename from SnapshotTests/AlertViewControllerTests.swift rename to SnapshotTests/AlertViewControllerVoiceOverTests.swift index 31e7e6c9b..c39ae40fe 100644 --- a/SnapshotTests/AlertViewControllerTests.swift +++ b/SnapshotTests/AlertViewControllerVoiceOverTests.swift @@ -3,18 +3,14 @@ import AccessibilitySnapshot import SnapshotTesting import XCTest -class AlertViewControllerTests: SnapshotTestCase { +final class AlertViewControllerVoiceOverTests: SnapshotTestCase { func test_screenSharingOffer() { let alert = alert(ofKind: .screenShareOffer( .mock(), accepted: {}, declined: {} )) - assertSnapshot( - matching: alert, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + alert.assertSnapshot(as: .accessibilityImage) } func test_mediaUpgradeOffer() { @@ -23,11 +19,7 @@ class AlertViewControllerTests: SnapshotTestCase { accepted: {}, declined: {} )) - assertSnapshot( - matching: alert, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + alert.assertSnapshot(as: .accessibilityImage) } func test_messageAlert() { @@ -36,11 +28,7 @@ class AlertViewControllerTests: SnapshotTestCase { accessibilityIdentifier: nil, dismissed: {} )) - assertSnapshot( - matching: alert, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + alert.assertSnapshot(as: .accessibilityImage) } func test_singleAction() { @@ -49,11 +37,7 @@ class AlertViewControllerTests: SnapshotTestCase { accessibilityIdentifier: "mocked-accessibility-identifier", actionTapped: {} )) - assertSnapshot( - matching: alert, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + alert.assertSnapshot(as: .accessibilityImage) } private func alert(ofKind kind: AlertViewController.Kind) -> AlertViewController { @@ -61,7 +45,6 @@ class AlertViewControllerTests: SnapshotTestCase { kind: kind, viewFactory: .mock() ) - viewController.view.frame = UIScreen.main.bounds return viewController } } diff --git a/SnapshotTests/BubbleViewDynamicTypeFontTests.swift b/SnapshotTests/BubbleViewDynamicTypeFontTests.swift index cb60ee7d2..89bfab933 100644 --- a/SnapshotTests/BubbleViewDynamicTypeFontTests.swift +++ b/SnapshotTests/BubbleViewDynamicTypeFontTests.swift @@ -7,10 +7,7 @@ final class BubbleViewDynamicTypeFontTests: SnapshotTestCase { func test_bubble_extra3Large() { let bubble = ViewFactory.mock().makeBubbleView() bubble.frame = .init(origin: .zero, size: .init(width: 50, height: 50)) - // If shadow will cause failing test locally or on CI, we should disable it. - assertSnapshot( - matching: bubble, - as: .extra3LargeFontStrategy - ) + bubble.assertSnapshot(as: .extra3LargeFont, in: .portrait) + bubble.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } diff --git a/SnapshotTests/BubbleViewLayoutTests.swift b/SnapshotTests/BubbleViewLayoutTests.swift index 47538040f..d18734882 100644 --- a/SnapshotTests/BubbleViewLayoutTests.swift +++ b/SnapshotTests/BubbleViewLayoutTests.swift @@ -2,11 +2,11 @@ import SnapshotTesting import XCTest -class BubbleViewLayoutTests: SnapshotTestCase { +final class BubbleViewLayoutTests: SnapshotTestCase { func test_bubble() { let bubble = ViewFactory.mock().makeBubbleView() bubble.frame = .init(origin: .zero, size: .init(width: 50, height: 50)) - // If shadow will cause failing test locally or on CI, we should disable it. - assertSnapshot(matching: bubble, as: .image) + bubble.assertSnapshot(as: .image, in: .portrait) + bubble.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/BubbleViewVoiceOverTests.swift b/SnapshotTests/BubbleViewVoiceOverTests.swift index c9b99f7ab..9bda941f6 100644 --- a/SnapshotTests/BubbleViewVoiceOverTests.swift +++ b/SnapshotTests/BubbleViewVoiceOverTests.swift @@ -3,11 +3,10 @@ import AccessibilitySnapshot import SnapshotTesting import XCTest -class BubbleViewVoiceOverTests: SnapshotTestCase { +final class BubbleViewVoiceOverTests: SnapshotTestCase { func test_bubble() { let bubble = ViewFactory.mock().makeBubbleView() bubble.frame = .init(origin: .zero, size: .init(width: 50, height: 50)) - // If shadow will cause failing test locally or on CI, we should disable it. - assertSnapshot(matching: bubble, as: .accessibilityImage(precision: SnapshotTestCase.possiblePrecision)) + bubble.assertSnapshot(as: .accessibilityImage) } } diff --git a/SnapshotTests/BubbleWindowLayoutTests.swift b/SnapshotTests/BubbleWindowLayoutTests.swift index fc827cc23..5640e3413 100644 --- a/SnapshotTests/BubbleWindowLayoutTests.swift +++ b/SnapshotTests/BubbleWindowLayoutTests.swift @@ -14,6 +14,7 @@ final class BubbleWindowLayoutTests: XCTestCase { ) ) bubbleWindow.makeKeyAndVisible() - assertSnapshot(matching: bubbleWindow, as: .image) + bubbleWindow.assertSnapshot(as: .image, in: .portrait) + bubbleWindow.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/CallViewControllerDynamicTypeFontTests.swift b/SnapshotTests/CallViewControllerDynamicTypeFontTests.swift index eb7ff9b11..7d3dbcd41 100644 --- a/SnapshotTests/CallViewControllerDynamicTypeFontTests.swift +++ b/SnapshotTests/CallViewControllerDynamicTypeFontTests.swift @@ -5,61 +5,37 @@ import XCTest final class CallViewControllerDynamicTypeFontTests: SnapshotTestCase { func test_audioCallQueueState_extra3Large() throws { let viewController = try CallViewController.mockAudioCallQueueState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_audioCallConnectingState_extra3Large() throws { let viewController = try CallViewController.mockAudioCallConnectingState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_audioCallConnectedState_extra3Large() throws { let viewController = try CallViewController.mockAudioCallConnectedState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_mockVideoCallConnectingState_extra3Large() throws { let viewController = try CallViewController.mockVideoCallConnectingState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_mockVideoCallQueueState_extra3Large() throws { let viewController = try CallViewController.mockVideoCallQueueState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_mockVideoCallConnectedState_extra3Large() throws { let viewController = try CallViewController.mockVideoCallConnectedState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } diff --git a/SnapshotTests/CallViewControllerLayoutTests.swift b/SnapshotTests/CallViewControllerLayoutTests.swift index 2093dec4d..922f102d0 100644 --- a/SnapshotTests/CallViewControllerLayoutTests.swift +++ b/SnapshotTests/CallViewControllerLayoutTests.swift @@ -2,64 +2,40 @@ import SnapshotTesting import XCTest -class CallViewControllerLayoutTests: SnapshotTestCase { +final class CallViewControllerLayoutTests: SnapshotTestCase { func test_audioCallQueueState() throws { let viewController = try CallViewController.mockAudioCallQueueState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_audioCallConnectingState() throws { let viewController = try CallViewController.mockAudioCallConnectingState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_audioCallConnectedState() throws { let viewController = try CallViewController.mockAudioCallConnectedState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_mockVideoCallConnectingState() throws { let viewController = try CallViewController.mockVideoCallConnectingState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_mockVideoCallQueueState() throws { let viewController = try CallViewController.mockVideoCallQueueState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_mockVideoCallConnectedState() throws { let viewController = try CallViewController.mockVideoCallConnectedState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/CallViewControllerVoiceOverTests.swift b/SnapshotTests/CallViewControllerVoiceOverTests.swift index 316dbb008..2fab3b6ad 100644 --- a/SnapshotTests/CallViewControllerVoiceOverTests.swift +++ b/SnapshotTests/CallViewControllerVoiceOverTests.swift @@ -3,64 +3,34 @@ import AccessibilitySnapshot import SnapshotTesting import XCTest -class CallViewControllerVoiceOverTests: SnapshotTestCase { +final class CallViewControllerVoiceOverTests: SnapshotTestCase { func test_audioCallQueueState() throws { let viewController = try CallViewController.mockAudioCallQueueState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_audioCallConnectingState() throws { let viewController = try CallViewController.mockAudioCallConnectingState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_audioCallConnectedState() throws { let viewController = try CallViewController.mockAudioCallConnectedState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_mockVideoCallConnectingState() throws { let viewController = try CallViewController.mockVideoCallConnectingState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_mockVideoCallQueueState() throws { let viewController = try CallViewController.mockVideoCallQueueState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_mockVideoCallConnectedState() throws { let viewController = try CallViewController.mockVideoCallConnectedState() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } } diff --git a/SnapshotTests/ChatCallUpgradeViewDynamicTypeFontTests.swift b/SnapshotTests/ChatCallUpgradeViewDynamicTypeFontTests.swift index d7dd5efaa..e968e7125 100644 --- a/SnapshotTests/ChatCallUpgradeViewDynamicTypeFontTests.swift +++ b/SnapshotTests/ChatCallUpgradeViewDynamicTypeFontTests.swift @@ -6,18 +6,14 @@ final class ChatCallUpgradeViewDynamicTypeFontTests: SnapshotTestCase { func test_chatCallUpgradeViewToAudio_extra3Large() { let upgradeView = ChatCallUpgradeView(with: Theme.mock().chat.audioUpgrade, duration: .init(with: .zero)) upgradeView.frame = .init(origin: .zero, size: .init(width: 300, height: 160)) - assertSnapshot( - matching: upgradeView, - as: .extra3LargeFontStrategy - ) + upgradeView.assertSnapshot(as: .extra3LargeFont, in: .portrait) + upgradeView.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_chatCallUpgradeViewToVideo_extra3Large() { let upgradeView = ChatCallUpgradeView(with: Theme.mock().chat.videoUpgrade, duration: .init(with: .zero)) upgradeView.frame = .init(origin: .zero, size: .init(width: 300, height: 160)) - assertSnapshot( - matching: upgradeView, - as: .extra3LargeFontStrategy - ) + upgradeView.assertSnapshot(as: .extra3LargeFont, in: .portrait) + upgradeView.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } diff --git a/SnapshotTests/ChatCallUpgradeViewLayoutTests.swift b/SnapshotTests/ChatCallUpgradeViewLayoutTests.swift index e9d4a210e..161d4a1ff 100644 --- a/SnapshotTests/ChatCallUpgradeViewLayoutTests.swift +++ b/SnapshotTests/ChatCallUpgradeViewLayoutTests.swift @@ -2,22 +2,18 @@ import SnapshotTesting import XCTest -class ChatCallUpgradeViewLayoutTests: SnapshotTestCase { +final class ChatCallUpgradeViewLayoutTests: SnapshotTestCase { func test_chatCallUpgradeViewToAudio() { let upgradeView = ChatCallUpgradeView(with: Theme.mock().chat.audioUpgrade, duration: .init(with: .zero)) upgradeView.frame = .init(origin: .zero, size: .init(width: 300, height: 120)) - assertSnapshot( - matching: upgradeView, - as: .image - ) + upgradeView.assertSnapshot(as: .image, in: .portrait) + upgradeView.assertSnapshot(as: .image, in: .landscape) } func test_chatCallUpgradeViewToVideo() { let upgradeView = ChatCallUpgradeView(with: Theme.mock().chat.videoUpgrade, duration: .init(with: .zero)) upgradeView.frame = .init(origin: .zero, size: .init(width: 300, height: 120)) - assertSnapshot( - matching: upgradeView, - as: .image - ) + upgradeView.assertSnapshot(as: .image, in: .portrait) + upgradeView.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/ChatCallUpgradeViewVoiceOverTests.swift b/SnapshotTests/ChatCallUpgradeViewVoiceOverTests.swift index 34110d6c2..e7a74981d 100644 --- a/SnapshotTests/ChatCallUpgradeViewVoiceOverTests.swift +++ b/SnapshotTests/ChatCallUpgradeViewVoiceOverTests.swift @@ -3,22 +3,16 @@ import AccessibilitySnapshot import SnapshotTesting import XCTest -class ChatCallUpgradeViewVoiceOverTests: SnapshotTestCase { +final class ChatCallUpgradeViewVoiceOverTests: SnapshotTestCase { func test_chatCallUpgradeViewToAudio() { let upgradeView = ChatCallUpgradeView(with: Theme.mock().chat.audioUpgrade, duration: .init(with: .zero)) upgradeView.frame = .init(origin: .zero, size: .init(width: 300, height: 120)) - assertSnapshot( - matching: upgradeView, - as: .accessibilityImage(precision: SnapshotTestCase.possiblePrecision) - ) + upgradeView.assertSnapshot(as: .accessibilityImage) } func test_chatCallUpgradeViewToVideo() { let upgradeView = ChatCallUpgradeView(with: Theme.mock().chat.videoUpgrade, duration: .init(with: .zero)) upgradeView.frame = .init(origin: .zero, size: .init(width: 300, height: 120)) - assertSnapshot( - matching: upgradeView, - as: .accessibilityImage(precision: SnapshotTestCase.possiblePrecision) - ) + upgradeView.assertSnapshot(as: .accessibilityImage) } } diff --git a/SnapshotTests/ChatViewControllerDynamicTypeFontTests.swift b/SnapshotTests/ChatViewControllerDynamicTypeFontTests.swift index e502d74c0..62b3b34b4 100644 --- a/SnapshotTests/ChatViewControllerDynamicTypeFontTests.swift +++ b/SnapshotTests/ChatViewControllerDynamicTypeFontTests.swift @@ -5,32 +5,20 @@ import XCTest final class ChatViewControllerDynamicTypeFontTests: SnapshotTestCase { func test_messagesFromHistory_extra3Large() { let viewController = ChatViewController.mockHistoryMessagesScreen() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_visitorUploadedFileStates_extra3Large() throws { let viewController = try ChatViewController.mockVisitorFileUploadStates() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_choiceCard_extra3Large() throws { let viewController = try ChatViewController.mockChoiceCard() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_visitorFileDownloadStates_extra3Large() throws { @@ -46,10 +34,7 @@ final class ChatViewControllerDynamicTypeFontTests: SnapshotTestCase { chatMessages[1].downloads[0].state.value = .downloading(progress: .init(with: 0.5)) chatMessages[2].downloads[0].state.value = .downloaded(.mock()) chatMessages[3].downloads[0].state.value = .error(.deleted) - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } diff --git a/SnapshotTests/ChatViewControllerLayoutTests.swift b/SnapshotTests/ChatViewControllerLayoutTests.swift index 6b8a371b4..10480549a 100644 --- a/SnapshotTests/ChatViewControllerLayoutTests.swift +++ b/SnapshotTests/ChatViewControllerLayoutTests.swift @@ -2,65 +2,41 @@ import SnapshotTesting import XCTest -class ChatViewControllerLayoutTests: SnapshotTestCase { +final class ChatViewControllerLayoutTests: SnapshotTestCase { func test_messagesFromHistory() { let viewController = ChatViewController.mockHistoryMessagesScreen() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_visitorUploadedFileStates() throws { let viewController = try ChatViewController.mockVisitorFileUploadStates() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_choiceCard() throws { let viewController = try ChatViewController.mockChoiceCard() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_gvaPersistentButton() throws { let viewController = try ChatViewController.mockGvaPersistentButton() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_gvaResponseText() throws { let viewController = try ChatViewController.mockGvaResponseText() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_gvaGalleryCard() throws { let viewController = try ChatViewController.mockGvaGalleryCards() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_gvaQuickReply() throws { @@ -76,11 +52,8 @@ class ChatViewControllerLayoutTests: SnapshotTestCase { ) view.frame = .init(origin: .zero, size: .init(width: 350, height: 200)) view.collectionView.heightAnchor.constraint(equalToConstant: 200).isActive = true - assertSnapshot( - matching: view, - as: .image, - named: self.nameForDevice() - ) + view.assertSnapshot(as: .image, in: .portrait) + view.assertSnapshot(as: .image, in: .landscape) } func test_visitorFileDownloadStates() throws { @@ -96,10 +69,7 @@ class ChatViewControllerLayoutTests: SnapshotTestCase { chatMessages[1].downloads[0].state.value = .downloading(progress: .init(with: 0.5)) chatMessages[2].downloads[0].state.value = .downloaded(.mock()) chatMessages[3].downloads[0].state.value = .error(.deleted) - assertSnapshot( - matching: viewController, - as: .image, - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/ChatViewControllerVoiceOverTests.swift b/SnapshotTests/ChatViewControllerVoiceOverTests.swift index d41d9457b..c08c7a92a 100644 --- a/SnapshotTests/ChatViewControllerVoiceOverTests.swift +++ b/SnapshotTests/ChatViewControllerVoiceOverTests.swift @@ -3,35 +3,20 @@ import AccessibilitySnapshot import SnapshotTesting import XCTest -class ChatViewControllerVoiceOverTests: SnapshotTestCase { +final class ChatViewControllerVoiceOverTests: SnapshotTestCase { func test_messagesFromHistory() { let viewController = ChatViewController.mockHistoryMessagesScreen() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_visitorUploadedFileStates() throws { let viewController = try ChatViewController.mockVisitorFileUploadStates() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_choiceCard() throws { let viewController = try ChatViewController.mockChoiceCard() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_visitorFileDownloadStates() throws { @@ -47,41 +32,22 @@ class ChatViewControllerVoiceOverTests: SnapshotTestCase { chatMessages[1].downloads[0].state.value = .downloading(progress: .init(with: 0.5)) chatMessages[2].downloads[0].state.value = .downloaded(.mock()) chatMessages[3].downloads[0].state.value = .error(.deleted) - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_gvaPersistentButton() throws { let viewController = try ChatViewController.mockGvaPersistentButton() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_gvaResponseText() throws { let viewController = try ChatViewController.mockGvaResponseText() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_gvaGalleryCard() throws { let viewController = try ChatViewController.mockGvaGalleryCards() - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_gvaQuickReply() throws { @@ -97,10 +63,6 @@ class ChatViewControllerVoiceOverTests: SnapshotTestCase { ) view.frame = .init(origin: .zero, size: .init(width: 350, height: 200)) view.collectionView.heightAnchor.constraint(equalToConstant: 200).isActive = true - assertSnapshot( - matching: view, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: self.nameForDevice() - ) + view.assertSnapshot(as: .accessibilityImage) } } diff --git a/SnapshotTests/ScreenShareViewControllerDynamicTypeFontTests.swift b/SnapshotTests/ScreenShareViewControllerDynamicTypeFontTests.swift index efdbee770..1df4baf23 100644 --- a/SnapshotTests/ScreenShareViewControllerDynamicTypeFontTests.swift +++ b/SnapshotTests/ScreenShareViewControllerDynamicTypeFontTests.swift @@ -5,29 +5,10 @@ import XCTest // swiftlint:disable type_name final class ScreenShareViewControllerDynamicTypeFontTests: SnapshotTestCase { func testScreenShareViewController_extra3Large() { - let theme = Theme() - let font = theme.font - let props: CallVisualizer.ScreenSharingViewController.Props = .init( - screenSharingViewProps: .init( - style: .mock( - messageTextFont: font.header2, - buttonTitleFont: font.bodyText - ), - header: .mock( - title: L10n.CallVisualizer.ScreenSharing.title, - backButton: .init(style: .mock(image: Asset.back.image)) - ), - endScreenSharing: .mock(style: .mock(titleFont: font.bodyText)) - ) - ) - let screenShareViewController = CallVisualizer.ScreenSharingViewController(props: props) - screenShareViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: screenShareViewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + let model: CallVisualizer.ScreenSharingView.Model = .mock() + let viewController: CallVisualizer.ScreenSharingViewController = .init(model: model) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } // swiftlint:enable type_name diff --git a/SnapshotTests/ScreenShareViewControllerLayoutTests.swift b/SnapshotTests/ScreenShareViewControllerLayoutTests.swift index 3cdb38c4c..cd210b7f0 100644 --- a/SnapshotTests/ScreenShareViewControllerLayoutTests.swift +++ b/SnapshotTests/ScreenShareViewControllerLayoutTests.swift @@ -2,30 +2,11 @@ import SnapshotTesting import XCTest -class ScreenShareViewControllerLayoutTests: SnapshotTestCase { +final class ScreenShareViewControllerLayoutTests: SnapshotTestCase { func testScreenShareViewController() { - let theme = Theme() - let font = theme.font - let props: CallVisualizer.ScreenSharingViewController.Props = .init( - screenSharingViewProps: .init( - style: .mock( - messageTextFont: font.header2, - buttonTitleFont: font.bodyText - ), - header: .mock( - title: L10n.CallVisualizer.ScreenSharing.title, - backButton: .init(style: .mock(image: Asset.back.image)) - ), - endScreenSharing: .mock(style: .mock(titleFont: font.bodyText)) - ) - ) - let screenShareViewController = CallVisualizer.ScreenSharingViewController(props: props) - screenShareViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: screenShareViewController, - as: .image, - named: nameForDevice() - ) + let model: CallVisualizer.ScreenSharingView.Model = .mock() + let viewController: CallVisualizer.ScreenSharingViewController = .init(model: model) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/ScreenShareViewControllerTests.swift b/SnapshotTests/ScreenShareViewControllerTests.swift deleted file mode 100644 index 5e9dd973c..000000000 --- a/SnapshotTests/ScreenShareViewControllerTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -import AccessibilitySnapshot -@testable import GliaWidgets -import SnapshotTesting -import XCTest - -class ScreenShareViewControllerTests: SnapshotTestCase { - func testScreenShareViewController() { - let theme = Theme() - let font = theme.font - let props: CallVisualizer.ScreenSharingViewController.Props = .init( - screenSharingViewProps: .init( - style: .mock( - messageTextFont: font.header2, - buttonTitleFont: font.bodyText - ), - header: .mock( - title: L10n.CallVisualizer.ScreenSharing.title, - backButton: .init(style: .mock(image: Asset.back.image)) - ), - endScreenSharing: .mock(style: .mock(titleFont: font.bodyText)) - ) - ) - let screenShareViewController = CallVisualizer.ScreenSharingViewController(props: props) - screenShareViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: screenShareViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) - } -} diff --git a/SnapshotTests/ScreenShareViewControllerVoiceOverTests.swift b/SnapshotTests/ScreenShareViewControllerVoiceOverTests.swift new file mode 100644 index 000000000..b124aa601 --- /dev/null +++ b/SnapshotTests/ScreenShareViewControllerVoiceOverTests.swift @@ -0,0 +1,12 @@ +import AccessibilitySnapshot +@testable import GliaWidgets +import SnapshotTesting +import XCTest + +final class ScreenShareViewControllerVoiceOverTests: SnapshotTestCase { + func testScreenShareViewController() { + let model: CallVisualizer.ScreenSharingView.Model = .mock() + let viewController: CallVisualizer.ScreenSharingViewController = .init(model: model) + viewController.assertSnapshot(as: .accessibilityImage) + } +} diff --git a/SnapshotTests/SecureConversationsConfirmationScreenDynamicTypeFontTests.swift b/SnapshotTests/SecureConversationsConfirmationScreenDynamicTypeFontTests.swift index 134c38231..9f11b5123 100644 --- a/SnapshotTests/SecureConversationsConfirmationScreenDynamicTypeFontTests.swift +++ b/SnapshotTests/SecureConversationsConfirmationScreenDynamicTypeFontTests.swift @@ -7,42 +7,16 @@ final class SecureConversationsConfirmationScreenDynamicTypeFontTests: SnapshotT let theme = Theme.mock() func test_confirmationView_extra3Large() { - let props = Self.makeConfirmationProps(style: theme.secureConversationsConfirmation) - let viewController = SecureConversations.ConfirmationViewController( - viewModel: .init(environment: .init(confirmationStyle: theme.defaultSecureConversationsConfirmationStyle)), - viewFactory: .mock(theme: theme, messageRenderer: nil, environment: .mock), - props: props - ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .extra3LargeFontStrategy, - named: self.nameForDevice() - ) - } - - // MARK: - Helpers - - static func headerProps() -> Header.Props { - .mock( - title: "Secure Conversations", - backButton: .init(style: .mock(image: Asset.back.image)), - closeButton: .init(style: .mock(image: Asset.close.image)) - ) - } - - static func makeConfirmationProps( - headerProps: Header.Props = headerProps(), - style: SecureConversations.ConfirmationStyle - ) -> SecureConversations.ConfirmationViewController.Props { - .init( - confirmationViewProps: .init( - style: style, - header: headerProps, - checkMessageButtonTap: .nop - ) + let model: SecureConversations.ConfirmationViewSwiftUI.Model = .init( + environment: .init( + orientationManager: .mock(), uiApplication: .mock + ), + style: theme.defaultSecureConversationsConfirmationStyle, + delegate: nil ) + let viewController = SecureConversations.ConfirmationViewController(model: model) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } // swiftlint:enable type_name diff --git a/SnapshotTests/SecureConversationsConfirmationScreenLayoutTests.swift b/SnapshotTests/SecureConversationsConfirmationScreenLayoutTests.swift index 41a2645fe..2fef40481 100644 --- a/SnapshotTests/SecureConversationsConfirmationScreenLayoutTests.swift +++ b/SnapshotTests/SecureConversationsConfirmationScreenLayoutTests.swift @@ -2,45 +2,19 @@ import SnapshotTesting import XCTest -class SecureConversationsConfirmationScreenLayoutTests: SnapshotTestCase { +final class SecureConversationsConfirmationScreenLayoutTests: SnapshotTestCase { let theme = Theme.mock() func test_confirmationView() { - let props = Self.makeConfirmationProps(style: theme.secureConversationsConfirmation) - let viewController = SecureConversations.ConfirmationViewController( - viewModel: .init(environment: .init(confirmationStyle: theme.defaultSecureConversationsConfirmationStyle)), - viewFactory: .mock(theme: theme, messageRenderer: nil, environment: .mock), - props: props - ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .image, - named: self.nameForDevice() - ) - } - - // MARK: - Helpers - - static func headerProps() -> Header.Props { - .mock( - title: "Secure Conversations", - backButton: .init(style: .mock(image: Asset.back.image)), - closeButton: .init(style: .mock(image: Asset.close.image)) - ) - } - - static func makeConfirmationProps( - headerProps: Header.Props = headerProps(), - style: SecureConversations.ConfirmationStyle - ) -> SecureConversations.ConfirmationViewController.Props { - .init( - confirmationViewProps: .init( - style: style, - header: headerProps, - checkMessageButtonTap: .nop - ) + let model: SecureConversations.ConfirmationViewSwiftUI.Model = .init( + environment: .init( + orientationManager: .mock(), uiApplication: .mock + ), + style: theme.defaultSecureConversationsConfirmationStyle, + delegate: nil ) + let viewController = SecureConversations.ConfirmationViewController(model: model) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/SecureConversationsConfirmationScreenTests.swift b/SnapshotTests/SecureConversationsConfirmationScreenTests.swift deleted file mode 100644 index 587292207..000000000 --- a/SnapshotTests/SecureConversationsConfirmationScreenTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -import AccessibilitySnapshot -@testable import GliaWidgets -import SnapshotTesting -import XCTest - -class SecureConversationsConfirmationScreenTests: SnapshotTestCase { - let theme = Theme.mock() - - func test_confirmationView() { - let props = Self.makeConfirmationProps(style: theme.secureConversationsConfirmation) - let viewController = SecureConversations.ConfirmationViewController( - viewModel: .init(environment: .init(confirmationStyle: theme.defaultSecureConversationsConfirmationStyle)), - viewFactory: .mock(theme: theme, messageRenderer: nil, environment: .mock), - props: props - ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: self.nameForDevice() - ) - } - - // MARK: - Helpers - - static func headerProps() -> Header.Props { - .mock( - title: "Secure Conversations", - backButton: .init(style: .mock(image: Asset.back.image)), - closeButton: .init(style: .mock(image: Asset.close.image)) - ) - } - - static func makeConfirmationProps( - headerProps: Header.Props = headerProps(), - style: SecureConversations.ConfirmationStyle - ) -> SecureConversations.ConfirmationViewController.Props { - .init( - confirmationViewProps: .init( - style: style, - header: headerProps, - checkMessageButtonTap: .nop - ) - ) - } -} diff --git a/SnapshotTests/SecureConversationsConfirmationScreenVoiceOverTests.swift b/SnapshotTests/SecureConversationsConfirmationScreenVoiceOverTests.swift new file mode 100644 index 000000000..577760d73 --- /dev/null +++ b/SnapshotTests/SecureConversationsConfirmationScreenVoiceOverTests.swift @@ -0,0 +1,20 @@ +import AccessibilitySnapshot +@testable import GliaWidgets +import SnapshotTesting +import XCTest + +final class SecureConversationsConfirmationScreenVoiceOverTests: SnapshotTestCase { + let theme = Theme.mock() + + func test_confirmationView() { + let model: SecureConversations.ConfirmationViewSwiftUI.Model = .init( + environment: .init( + orientationManager: .mock(), uiApplication: .mock + ), + style: theme.defaultSecureConversationsConfirmationStyle, + delegate: nil + ) + let viewController = SecureConversations.ConfirmationViewController(model: model) + viewController.assertSnapshot(as: .accessibilityImage) + } +} diff --git a/SnapshotTests/SecureConversationsWelcomeScreenDynamicTypeFontTests.swift b/SnapshotTests/SecureConversationsWelcomeScreenDynamicTypeFontTests.swift index 5bd896ffc..2c693f8db 100644 --- a/SnapshotTests/SecureConversationsWelcomeScreenDynamicTypeFontTests.swift +++ b/SnapshotTests/SecureConversationsWelcomeScreenDynamicTypeFontTests.swift @@ -16,13 +16,8 @@ final class SecureConversationsWelcomeScreenDynamicTypeFontTests: SnapshotTestCa props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .extra3LargeFontStrategy, - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_welcomeWithAttachments_extra3Large() { @@ -38,13 +33,8 @@ final class SecureConversationsWelcomeScreenDynamicTypeFontTests: SnapshotTestCa props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .extra3LargeFontStrategy, - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_welcomeViewController_withValidationError_extra3Large() { @@ -54,13 +44,8 @@ final class SecureConversationsWelcomeScreenDynamicTypeFontTests: SnapshotTestCa props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .extra3LargeFontStrategy, - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } // MARK: - Helpers diff --git a/SnapshotTests/SecureConversationsWelcomeScreenLayoutTests.swift b/SnapshotTests/SecureConversationsWelcomeScreenLayoutTests.swift index fa8beb3b8..3ad2d23c5 100644 --- a/SnapshotTests/SecureConversationsWelcomeScreenLayoutTests.swift +++ b/SnapshotTests/SecureConversationsWelcomeScreenLayoutTests.swift @@ -2,7 +2,7 @@ import SnapshotTesting import XCTest -class SecureConversationsWelcomeScreenLayoutTests: SnapshotTestCase { +final class SecureConversationsWelcomeScreenLayoutTests: SnapshotTestCase { let theme = Theme.mock() func test_welcomeView() { @@ -15,13 +15,8 @@ class SecureConversationsWelcomeScreenLayoutTests: SnapshotTestCase { props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .image, - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_welcomeWithAttachments() { @@ -37,13 +32,8 @@ class SecureConversationsWelcomeScreenLayoutTests: SnapshotTestCase { props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .image, - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_welcomeViewController_withValidationError() { @@ -53,13 +43,8 @@ class SecureConversationsWelcomeScreenLayoutTests: SnapshotTestCase { props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .image, - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } // MARK: - Helpers diff --git a/SnapshotTests/SecureConversationsWelcomeScreenTests.swift b/SnapshotTests/SecureConversationsWelcomeScreenVoiceOverTests.swift similarity index 82% rename from SnapshotTests/SecureConversationsWelcomeScreenTests.swift rename to SnapshotTests/SecureConversationsWelcomeScreenVoiceOverTests.swift index ec485af2f..0461fe44b 100644 --- a/SnapshotTests/SecureConversationsWelcomeScreenTests.swift +++ b/SnapshotTests/SecureConversationsWelcomeScreenVoiceOverTests.swift @@ -3,7 +3,7 @@ import AccessibilitySnapshot import SnapshotTesting import XCTest -class SecureConversationsWelcomeScreenTests: SnapshotTestCase { +final class SecureConversationsWelcomeScreenVoiceOverTests: SnapshotTestCase { let theme = Theme.mock() func test_welcomeView() { @@ -16,13 +16,7 @@ class SecureConversationsWelcomeScreenTests: SnapshotTestCase { props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_welcomeWithAttachments() { @@ -38,13 +32,7 @@ class SecureConversationsWelcomeScreenTests: SnapshotTestCase { props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_welcomeViewController_withValidationError() { @@ -54,13 +42,7 @@ class SecureConversationsWelcomeScreenTests: SnapshotTestCase { props: .welcome(props), environment: .init(gcd: .live, uiScreen: .mock, notificationCenter: .mock) ) - viewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: viewController.view, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: self.nameForDevice() - ) + viewController.assertSnapshot(as: .accessibilityImage) } // MARK: - Helpers diff --git a/SnapshotTests/SnapshotTestCase.swift b/SnapshotTests/SnapshotTestCase.swift index d6d46b3e2..126549834 100644 --- a/SnapshotTests/SnapshotTestCase.swift +++ b/SnapshotTests/SnapshotTestCase.swift @@ -50,15 +50,46 @@ extension SnapshotTestCase { } } - func nameForDevice(baseName: String? = nil) -> String { + /// This method sets the name for the snapshot tests by taking one parameter. + /// Depending of the case passed in, either prefix, suffix, or nothing + /// additional will be added to the the base name. The method returns a string. + func nameForDevice(_ baseName: NameForDevice = .portrait) -> String { let size = UIScreen.main.bounds.size let scale = UIScreen.main.scale let version = UIDevice.current.systemVersion let deviceName = "\(Int(size.width))x\(Int(size.height))-\(version)-\(Int(scale))x" - return [baseName, deviceName] - .compactMap { $0 } - .joined(separator: "-") + switch baseName { + case let .baseName(value): + return [value, deviceName] + .compactMap { $0 } + .joined(separator: "-") + case .portrait: + return "\(deviceName)" + case .landscape: + return "\(deviceName)-landscape" + } + } + + /// All available cases for the snapshot tests name. This Enum + /// conforms to ExpressibleByStringLiteral allowing a string to be + /// passed instead of a case, that will convert it to baseName case. + /// + /// - baseName(String?) case is a string that can be added as prefix + /// to the test name + /// + /// - portrait case serves as a default case for snapshot tests and + /// won't add anything additional to the name + /// + /// - landscape case will add suffix to the end of the name + enum NameForDevice: ExpressibleByStringLiteral { + case baseName(String?) + case portrait + case landscape + + init(stringLiteral value: String) { + self = .baseName(value) + } } } @@ -188,4 +219,58 @@ extension Snapshotting where Value == UIView, Format == UIImage { ) ) } + + static var extra3LargeFontStrategyLandscape: Self { + let traits = UITraitCollection(traitsFrom: [ + .init(preferredContentSizeCategory: .accessibilityExtraExtraExtraLarge) + ] + commonTraitCollection) + + return Self.image(traits: traits) + } + + static var imageLandscape: Self { + let traits = UITraitCollection(traitsFrom: [ + .init(preferredContentSizeCategory: .medium), + ] + commonTraitCollection) + + return Self.image(traits: traits) + } + + private static var commonTraitCollection: [UITraitCollection] = [ + .init(layoutDirection: .leftToRight), + .init(userInterfaceIdiom: .phone), + .init(horizontalSizeClass: .regular), + .init(verticalSizeClass: .compact), + ] +} + +extension Snapshotting where Value == UIViewController, Format == UIImage { + static var extra3LargeFontStrategyLandscape: Self { + let traits = UITraitCollection(traitsFrom: [ + .init(preferredContentSizeCategory: .accessibilityExtraExtraExtraLarge) + ] + commonTraitCollection) + let safeArea: UIEdgeInsets = .init(top: 0, left: 47, bottom: 21, right: 47) + let size: CGSize = .init(width: 844, height: 390) + let viewImageConfig: ViewImageConfig = .init(safeArea: safeArea, size: size, traits: traits) + + return Self.image(on: viewImageConfig) + } + + static var imageLandscape: Self { + let traits = UITraitCollection(traitsFrom: [ + .init(preferredContentSizeCategory: .medium), + ] + commonTraitCollection) + let safeArea: UIEdgeInsets = .init(top: 0, left: 47, bottom: 21, right: 47) + let size: CGSize = .init(width: 844, height: 390) + let viewImageConfig: ViewImageConfig = .init(safeArea: safeArea, size: size, traits: traits) + + return Self.image(on: viewImageConfig) + } + + private static var commonTraitCollection: [UITraitCollection] = [ + .init(layoutDirection: .leftToRight), + .init(userInterfaceIdiom: .phone), + .init(horizontalSizeClass: .regular), + .init(verticalSizeClass: .compact), + ] } diff --git a/SnapshotTests/SurveyViewControllerDynamicTypeFontTests.swift b/SnapshotTests/SurveyViewControllerDynamicTypeFontTests.swift index 2e7fe033a..0ff909f11 100644 --- a/SnapshotTests/SurveyViewControllerDynamicTypeFontTests.swift +++ b/SnapshotTests/SurveyViewControllerDynamicTypeFontTests.swift @@ -4,32 +4,32 @@ import XCTest final class SurveyViewControllerDynamicTypeFontTests: SnapshotTestCase { func test_emptySurvey_extra3Large() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .emptyPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .emptyPropsMock() ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_filledSurvey_extra3Large() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .filledPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .filledPropsMock() ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func test_emptySurveyErrorState_extra3Large() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .errorPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .errorPropsMock() ) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } diff --git a/SnapshotTests/SurveyViewControllerLayoutTests.swift b/SnapshotTests/SurveyViewControllerLayoutTests.swift index 17ca7fd41..58f85c817 100644 --- a/SnapshotTests/SurveyViewControllerLayoutTests.swift +++ b/SnapshotTests/SurveyViewControllerLayoutTests.swift @@ -2,34 +2,34 @@ import SnapshotTesting import XCTest -class SurveyViewControllerLayoutTests: SnapshotTestCase { +final class SurveyViewControllerLayoutTests: SnapshotTestCase { func test_emptySurvey() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .emptyPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .emptyPropsMock() ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_filledSurvey() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .filledPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .filledPropsMock() ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func test_emptySurveyErrorState() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .errorPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .image, - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .errorPropsMock() ) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/SurveyViewControllerVoiceOverTests.swift b/SnapshotTests/SurveyViewControllerVoiceOverTests.swift index e66ee3071..c82f891e7 100644 --- a/SnapshotTests/SurveyViewControllerVoiceOverTests.swift +++ b/SnapshotTests/SurveyViewControllerVoiceOverTests.swift @@ -3,34 +3,40 @@ import AccessibilitySnapshot import SnapshotTesting import XCTest -class SurveyViewControllerVoiceOverTests: SnapshotTestCase { +final class SurveyViewControllerVoiceOverTests: SnapshotTestCase { func test_emptySurvey() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .emptyPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .emptyPropsMock() ) + viewController.assertSnapshot(as: .accessibilityImage) + } + + func test_emptySurveyWithDefaultValue() { + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .emptyPropsMockWithDefaultValue() + ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_filledSurvey() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .filledPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .filledPropsMock() ) + viewController.assertSnapshot(as: .accessibilityImage) } func test_emptySurveyErrorState() { - let viewController = Survey.ViewController(viewFactory: .mock(), environment: .init(notificationCenter: .mock), props: .errorPropsMock()) - viewController.view.frame = UIScreen.main.bounds - assertSnapshot( - matching: viewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .errorPropsMock() ) + viewController.assertSnapshot(as: .accessibilityImage) } } diff --git a/SnapshotTests/VideoCallViewControllerDynamicTypeFontTests.swift b/SnapshotTests/VideoCallViewControllerDynamicTypeFontTests.swift index f9da12752..2e9f44ecc 100644 --- a/SnapshotTests/VideoCallViewControllerDynamicTypeFontTests.swift +++ b/SnapshotTests/VideoCallViewControllerDynamicTypeFontTests.swift @@ -12,29 +12,29 @@ final class VideoCallViewControllerDynamicTypeFontTests: SnapshotTestCase { videButton: .mock( inactive: .activeMock( image: Asset.callVideoActive.image, - title: L10n.Call.Buttons.Video.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Video.Active.label) + title: Localization.Engagement.Video.title, + accessibility: .init(label: Localization.General.selected) ) ), muteButton: .mock( inactive: .inactiveMock( image: Asset.callMuteInactive.image, - title: L10n.Call.Buttons.Mute.Inactive.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Mute.Inactive.label) + title: Localization.Call.Mute.button, + accessibility: .init(label: "") ) ), speakerButton: .mock( inactive: .inactiveMock( image: Asset.callSpeakerInactive.image, - title: L10n.Call.Buttons.Speaker.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Speaker.Inactive.label) + title: Localization.Call.Speaker.button, + accessibility: .init(label: "") ) ), minimizeButton: .mock( inactive: .inactiveMock( image: Asset.callMiminize.image, - title: L10n.Call.Buttons.Minimize.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Minimize.Inactive.label) + title: Localization.Engagement.MinimizeVideo.button, + accessibility: .init(label: "") ) ), badge: .mock() @@ -48,14 +48,9 @@ final class VideoCallViewControllerDynamicTypeFontTests: SnapshotTestCase { ) let props: CallVisualizer.VideoCallViewController.Props = .init(videoCallViewProps: videoCallViewProps) - let videoCallViewController: CallVisualizer.VideoCallViewController = .mock(props: props) - videoCallViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: videoCallViewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + let viewController: CallVisualizer.VideoCallViewController = .mock(props: props) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } // swiftlint:enable type_name diff --git a/SnapshotTests/VideoCallViewControllerLayoutTests.swift b/SnapshotTests/VideoCallViewControllerLayoutTests.swift index a535999f7..2efe05f40 100644 --- a/SnapshotTests/VideoCallViewControllerLayoutTests.swift +++ b/SnapshotTests/VideoCallViewControllerLayoutTests.swift @@ -2,7 +2,7 @@ import SnapshotTesting import XCTest -class VideoCallViewControllerLayoutTests: SnapshotTestCase { +final class VideoCallViewControllerLayoutTests: SnapshotTestCase { func testVideoCallViewController() { let videoCallViewProps: CallVisualizer.VideoCallView.Props = .mock( buttonBarProps: .mock( @@ -11,29 +11,29 @@ class VideoCallViewControllerLayoutTests: SnapshotTestCase { videButton: .mock( inactive: .activeMock( image: Asset.callVideoActive.image, - title: L10n.Call.Buttons.Video.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Video.Active.label) + title: Localization.Engagement.Video.title, + accessibility: .init(label: Localization.General.selected) ) ), muteButton: .mock( inactive: .inactiveMock( image: Asset.callMuteInactive.image, - title: L10n.Call.Buttons.Mute.Inactive.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Mute.Inactive.label) + title: Localization.Call.Mute.button, + accessibility: .init(label: "") ) ), speakerButton: .mock( inactive: .inactiveMock( image: Asset.callSpeakerInactive.image, - title: L10n.Call.Buttons.Speaker.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Speaker.Inactive.label) + title: Localization.Call.Speaker.button, + accessibility: .init(label: "") ) ), minimizeButton: .mock( inactive: .inactiveMock( image: Asset.callMiminize.image, - title: L10n.Call.Buttons.Minimize.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Minimize.Inactive.label) + title: Localization.Engagement.MinimizeVideo.button, + accessibility: .init(label: "") ) ), badge: .mock() @@ -47,13 +47,8 @@ class VideoCallViewControllerLayoutTests: SnapshotTestCase { ) let props: CallVisualizer.VideoCallViewController.Props = .init(videoCallViewProps: videoCallViewProps) - let videoCallViewController: CallVisualizer.VideoCallViewController = .mock(props: props) - videoCallViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: videoCallViewController, - as: .image, - named: nameForDevice() - ) + let viewController: CallVisualizer.VideoCallViewController = .mock(props: props) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/VideoCallViewControllerTests.swift b/SnapshotTests/VideoCallViewControllerVoiceOverTests.swift similarity index 57% rename from SnapshotTests/VideoCallViewControllerTests.swift rename to SnapshotTests/VideoCallViewControllerVoiceOverTests.swift index 6ca8ed446..df163f8ae 100644 --- a/SnapshotTests/VideoCallViewControllerTests.swift +++ b/SnapshotTests/VideoCallViewControllerVoiceOverTests.swift @@ -3,7 +3,7 @@ import AccessibilitySnapshot import SnapshotTesting import XCTest -class VideoCallViewControllerTests: SnapshotTestCase { +final class VideoCallViewControllerVoiceOverTests: SnapshotTestCase { func testVideoCallViewController() { let videoCallViewProps: CallVisualizer.VideoCallView.Props = .mock( buttonBarProps: .mock( @@ -12,29 +12,29 @@ class VideoCallViewControllerTests: SnapshotTestCase { videButton: .mock( inactive: .activeMock( image: Asset.callVideoActive.image, - title: L10n.Call.Buttons.Video.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Video.Active.label) + title: Localization.Engagement.Video.title, + accessibility: .init(label: Localization.General.selected) ) ), muteButton: .mock( inactive: .inactiveMock( image: Asset.callMuteInactive.image, - title: L10n.Call.Buttons.Mute.Inactive.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Mute.Inactive.label) + title: Localization.Call.Mute.button, + accessibility: .init(label: "") ) ), speakerButton: .mock( inactive: .inactiveMock( image: Asset.callSpeakerInactive.image, - title: L10n.Call.Buttons.Speaker.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Speaker.Inactive.label) + title: Localization.Call.Speaker.button, + accessibility: .init(label: "") ) ), minimizeButton: .mock( inactive: .inactiveMock( image: Asset.callMiminize.image, - title: L10n.Call.Buttons.Minimize.title, - accessibility: .init(label: L10n.Call.Accessibility.Buttons.Minimize.Inactive.label) + title: Localization.Engagement.MinimizeVideo.button, + accessibility: .init(label: "") ) ), badge: .mock() @@ -48,13 +48,7 @@ class VideoCallViewControllerTests: SnapshotTestCase { ) let props: CallVisualizer.VideoCallViewController.Props = .init(videoCallViewProps: videoCallViewProps) - let videoCallViewController: CallVisualizer.VideoCallViewController = .mock(props: props) - videoCallViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: videoCallViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + let viewController: CallVisualizer.VideoCallViewController = .mock(props: props) + viewController.assertSnapshot(as: .accessibilityImage) } } diff --git a/SnapshotTests/VisitorCodeViewControllerDynamicTypeFontTests.swift b/SnapshotTests/VisitorCodeViewControllerDynamicTypeFontTests.swift index accde95a0..313fa03fa 100644 --- a/SnapshotTests/VisitorCodeViewControllerDynamicTypeFontTests.swift +++ b/SnapshotTests/VisitorCodeViewControllerDynamicTypeFontTests.swift @@ -11,14 +11,9 @@ final class VisitorCodeViewControllerDynamicTypeFontTests: SnapshotTestCase { viewState: .loading ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func testVisitorCodeAlertWhenError() { @@ -28,14 +23,9 @@ final class VisitorCodeViewControllerDynamicTypeFontTests: SnapshotTestCase { viewState: .error(refreshTap: .nop) ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func testVisitorCodeAlertWhenSuccess() { @@ -45,14 +35,9 @@ final class VisitorCodeViewControllerDynamicTypeFontTests: SnapshotTestCase { viewState: .success(visitorCode: "12345") ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func testVisitorCodeEmbeddedWhenLoading() { @@ -62,14 +47,9 @@ final class VisitorCodeViewControllerDynamicTypeFontTests: SnapshotTestCase { viewState: .loading ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func testVisitorCodeEmbeddedWhenError() { @@ -79,14 +59,9 @@ final class VisitorCodeViewControllerDynamicTypeFontTests: SnapshotTestCase { viewState: .error(refreshTap: .nop) ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } func testVisitorCodeEmbeddedWhenSuccess() { let props: CallVisualizer.VisitorCodeViewController.Props = .init( @@ -95,14 +70,9 @@ final class VisitorCodeViewControllerDynamicTypeFontTests: SnapshotTestCase { viewState: .success(visitorCode: "12345") ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .extra3LargeFontStrategy, - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .extra3LargeFont, in: .portrait) + viewController.assertSnapshot(as: .extra3LargeFont, in: .landscape) } } // swiftlint:enable type_name diff --git a/SnapshotTests/VisitorCodeViewControllerLayoutTests.swift b/SnapshotTests/VisitorCodeViewControllerLayoutTests.swift index ddf554e06..08189628d 100644 --- a/SnapshotTests/VisitorCodeViewControllerLayoutTests.swift +++ b/SnapshotTests/VisitorCodeViewControllerLayoutTests.swift @@ -2,7 +2,7 @@ import SnapshotTesting import XCTest -class VisitorCodeViewControllerLayoutTests: SnapshotTestCase { +final class VisitorCodeViewControllerLayoutTests: SnapshotTestCase { func testVisitorCodeAlertWhenLoading() { let props: CallVisualizer.VisitorCodeViewController.Props = .init( visitorCodeViewProps: .init( @@ -10,14 +10,9 @@ class VisitorCodeViewControllerLayoutTests: SnapshotTestCase { viewState: .loading ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .image, - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func testVisitorCodeAlertWhenError() { @@ -27,14 +22,9 @@ class VisitorCodeViewControllerLayoutTests: SnapshotTestCase { viewState: .error(refreshTap: .nop) ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func testVisitorCodeAlertWhenSuccess() { @@ -44,14 +34,9 @@ class VisitorCodeViewControllerLayoutTests: SnapshotTestCase { viewState: .success(visitorCode: "12345") ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func testVisitorCodeEmbeddedWhenLoading() { @@ -61,14 +46,9 @@ class VisitorCodeViewControllerLayoutTests: SnapshotTestCase { viewState: .loading ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func testVisitorCodeEmbeddedWhenError() { @@ -78,14 +58,9 @@ class VisitorCodeViewControllerLayoutTests: SnapshotTestCase { viewState: .error(refreshTap: .nop) ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } func testVisitorCodeEmbeddedWhenSuccess() { let props: CallVisualizer.VisitorCodeViewController.Props = .init( @@ -94,13 +69,8 @@ class VisitorCodeViewControllerLayoutTests: SnapshotTestCase { viewState: .success(visitorCode: "12345") ) ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .image, in: .portrait) + viewController.assertSnapshot(as: .image, in: .landscape) } } diff --git a/SnapshotTests/VisitorCodeViewControllerTests.swift b/SnapshotTests/VisitorCodeViewControllerTests.swift deleted file mode 100644 index fe71f8a6a..000000000 --- a/SnapshotTests/VisitorCodeViewControllerTests.swift +++ /dev/null @@ -1,107 +0,0 @@ -import AccessibilitySnapshot -@testable import GliaWidgets -import SnapshotTesting -import XCTest - -class VisitorCodeViewControllerTests: SnapshotTestCase { - func testVisitorCodeAlertWhenLoading() { - let props: CallVisualizer.VisitorCodeViewController.Props = .init( - visitorCodeViewProps: .init( - viewType: .alert(closeButtonTap: .nop), - viewState: .loading - ) - ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) - } - - func testVisitorCodeAlertWhenError() { - let props: CallVisualizer.VisitorCodeViewController.Props = .init( - visitorCodeViewProps: .init( - viewType: .alert(closeButtonTap: .nop), - viewState: .error(refreshTap: .nop) - ) - ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) - } - - func testVisitorCodeAlertWhenSuccess() { - let props: CallVisualizer.VisitorCodeViewController.Props = .init( - visitorCodeViewProps: .init( - viewType: .alert(closeButtonTap: .nop), - viewState: .success(visitorCode: "12345") - ) - ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) - } - - func testVisitorCodeEmbeddedWhenLoading() { - let props: CallVisualizer.VisitorCodeViewController.Props = .init( - visitorCodeViewProps: .init( - viewType: .embedded, - viewState: .loading - ) - ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) - } - - func testVisitorCodeEmbeddedWhenError() { - let props: CallVisualizer.VisitorCodeViewController.Props = .init( - visitorCodeViewProps: .init( - viewType: .embedded, - viewState: .error(refreshTap: .nop) - ) - ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) - } - func testVisitorCodeEmbeddedWhenSuccess() { - let props: CallVisualizer.VisitorCodeViewController.Props = .init( - visitorCodeViewProps: .init( - viewType: .embedded, - viewState: .success(visitorCode: "12345") - ) - ) - let visitorCodeViewController = CallVisualizer.VisitorCodeViewController(props: props) - visitorCodeViewController.view.frame = UIScreen.main.bounds - - assertSnapshot( - matching: visitorCodeViewController, - as: .accessibilityImage(precision: Self.possiblePrecision), - named: nameForDevice() - ) - } -} diff --git a/SnapshotTests/VisitorCodeViewControllerVoiceOverTests.swift b/SnapshotTests/VisitorCodeViewControllerVoiceOverTests.swift new file mode 100644 index 000000000..8f8cbe0d1 --- /dev/null +++ b/SnapshotTests/VisitorCodeViewControllerVoiceOverTests.swift @@ -0,0 +1,71 @@ +import AccessibilitySnapshot +@testable import GliaWidgets +import SnapshotTesting +import XCTest + +final class VisitorCodeViewControllerVoiceOverTests: SnapshotTestCase { + func testVisitorCodeAlertWhenLoading() { + let props: CallVisualizer.VisitorCodeViewController.Props = .init( + visitorCodeViewProps: .init( + viewType: .alert(closeButtonTap: .nop), + viewState: .loading + ) + ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .accessibilityImage) + } + + func testVisitorCodeAlertWhenError() { + let props: CallVisualizer.VisitorCodeViewController.Props = .init( + visitorCodeViewProps: .init( + viewType: .alert(closeButtonTap: .nop), + viewState: .error(refreshTap: .nop) + ) + ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .accessibilityImage) + } + + func testVisitorCodeAlertWhenSuccess() { + let props: CallVisualizer.VisitorCodeViewController.Props = .init( + visitorCodeViewProps: .init( + viewType: .alert(closeButtonTap: .nop), + viewState: .success(visitorCode: "12345") + ) + ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .accessibilityImage) + } + + func testVisitorCodeEmbeddedWhenLoading() { + let props: CallVisualizer.VisitorCodeViewController.Props = .init( + visitorCodeViewProps: .init( + viewType: .embedded, + viewState: .loading + ) + ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .accessibilityImage) + } + + func testVisitorCodeEmbeddedWhenError() { + let props: CallVisualizer.VisitorCodeViewController.Props = .init( + visitorCodeViewProps: .init( + viewType: .embedded, + viewState: .error(refreshTap: .nop) + ) + ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .accessibilityImage) + } + func testVisitorCodeEmbeddedWhenSuccess() { + let props: CallVisualizer.VisitorCodeViewController.Props = .init( + visitorCodeViewProps: .init( + viewType: .embedded, + viewState: .success(visitorCode: "12345") + ) + ) + let viewController = CallVisualizer.VisitorCodeViewController(props: props) + viewController.assertSnapshot(as: .accessibilityImage) + } +} diff --git a/SnapshotTests/extensions/UIView+Extensions.swift b/SnapshotTests/extensions/UIView+Extensions.swift new file mode 100644 index 000000000..efc142e2b --- /dev/null +++ b/SnapshotTests/extensions/UIView+Extensions.swift @@ -0,0 +1,66 @@ +import UIKit +import SnapshotTesting + +extension UIView { + func assertSnapshot( + as mode: SnapshotMode, + in orientation: SnapshotOrientation = .portrait, + named name: String? = nil, + record recording: Bool = false, + file: StaticString = #file, + functionName: String = #function, + line: UInt = #line + ) { + let snapshotting: Snapshotting + switch mode { + case .accessibilityImage: + snapshotting = .accessibilityImage(precision: SnapshotTestCase.possiblePrecision) + case .image: + snapshotting = orientation == .portrait ? .image : .imageLandscape + case .extra3LargeFont: + snapshotting = orientation == .portrait ? .extra3LargeFontStrategy : .extra3LargeFontStrategyLandscape + } + let snapshotName = snapshotName(name, orientation: orientation) + SnapshotTesting.assertSnapshot( + matching: self, + as: snapshotting, + named: snapshotName, + record: recording, + file: file, + testName: functionName, + line: line + ) + } + + func snapshotName( + _ baseName: String? = nil, + orientation: SnapshotOrientation + ) -> String { + let size = UIScreen.main.bounds.size + let scale = UIScreen.main.scale + let version = UIDevice.current.systemVersion + let deviceName = "\(Int(size.width))x\(Int(size.height))-\(version)-\(Int(scale))x" + + switch orientation { + case .portrait: + return [baseName, deviceName] + .compactMap { $0 } + .joined(separator: "-") + case .landscape: + return [baseName, deviceName, "landscape"] + .compactMap { $0 } + .joined(separator: "-") + } + } + + enum SnapshotOrientation { + case portrait + case landscape + } + + enum SnapshotMode { + case accessibilityImage + case image + case extra3LargeFont + } +} diff --git a/SnapshotTests/extensions/UIViewController+Extensions.swift b/SnapshotTests/extensions/UIViewController+Extensions.swift new file mode 100644 index 000000000..c5c81f345 --- /dev/null +++ b/SnapshotTests/extensions/UIViewController+Extensions.swift @@ -0,0 +1,68 @@ +import UIKit +import SnapshotTesting + +extension UIViewController { + func assertSnapshot( + as mode: SnapshotMode, + in orientation: SnapshotOrientation = .portrait, + bounds: CGRect = UIScreen.main.bounds, + named name: String? = nil, + record recording: Bool = false, + file: StaticString = #file, + functionName: String = #function, + line: UInt = #line + ) { + self.view.bounds = bounds + let snapshotting: Snapshotting + switch mode { + case .accessibilityImage: + snapshotting = .accessibilityImage(precision: SnapshotTestCase.possiblePrecision) + case .image: + snapshotting = orientation == .portrait ? .image : .imageLandscape + case .extra3LargeFont: + snapshotting = orientation == .portrait ? .extra3LargeFontStrategy : .extra3LargeFontStrategyLandscape + } + let snapshotName = snapshotName(name, orientation: orientation) + SnapshotTesting.assertSnapshot( + matching: self, + as: snapshotting, + named: snapshotName, + record: recording, + file: file, + testName: functionName, + line: line + ) + } + + func snapshotName( + _ baseName: String? = nil, + orientation: SnapshotOrientation + ) -> String { + let size = UIScreen.main.bounds.size + let scale = UIScreen.main.scale + let version = UIDevice.current.systemVersion + let deviceName = "\(Int(size.width))x\(Int(size.height))-\(version)-\(Int(scale))x" + + switch orientation { + case .portrait: + return [baseName, deviceName] + .compactMap { $0 } + .joined(separator: "-") + case .landscape: + return [baseName, deviceName, "landscape"] + .compactMap { $0 } + .joined(separator: "-") + } + } + + enum SnapshotOrientation { + case portrait + case landscape + } + + enum SnapshotMode { + case accessibilityImage + case image + case extra3LargeFont + } +} diff --git a/TestingApp/Info.plist b/TestingApp/Info.plist index a20418bc5..0dd89997d 100644 --- a/TestingApp/Info.plist +++ b/TestingApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.1.0 + 2.2.0 CFBundleURLTypes diff --git a/TestingApp/Main.storyboard b/TestingApp/Main.storyboard index f573b303d..dea23e613 100644 --- a/TestingApp/Main.storyboard +++ b/TestingApp/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -21,7 +21,7 @@ - + + + + + + + + + + + - + - +