From a62c96f1c51d2638180be7c8b8be5193d9185e1a Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 21 Mar 2024 14:01:23 +0200 Subject: [PATCH] Onboarding flow coordinator and FTUE changes (#2578) Fixes #2595, fixes #2594, fixes #2593, fixes #2592, fixes #2591 --- ElementX.xcodeproj/project.pbxproj | 280 +++++++++----- .../background-bottom.imageset/Contents.json | 22 ++ .../background-bottom-dark.png | Bin 0 -> 96298 bytes .../background-bottom-light.png | Bin 0 -> 57376 bytes .../Alerts.svg | 57 +++ .../Contents.json | 15 + .../en.lproj/Localizable.strings | 4 + .../Sources/Application/AppCoordinator.swift | 68 ++-- .../AppCoordinatorStateMachine.swift | 22 +- .../Sources/Application/AppSettings.swift | 29 +- .../AuthenticationFlowCoordinator.swift | 60 +-- .../OnboardingFlowCoordinator.swift | 343 ++++++++++++++++++ .../RoomFlowCoordinator.swift | 6 +- .../SettingsFlowCoordinator.swift | 32 -- .../UserSessionFlowCoordinator.swift | 80 ++-- ...erSessionFlowCoordinatorStateMachine.swift | 13 - ElementX/Sources/Generated/Assets.swift | 2 + ElementX/Sources/Generated/Strings.swift | 8 + ElementX/Sources/Other/ProcessInfo.swift | 10 +- .../SwiftUI/Layout/FullscreenDialog.swift | 33 +- .../Other/SwiftUI/Views/HeroImage.swift | 36 +- .../SwiftUI/Views}/RoundedLabelItem.swift | 0 .../LoginScreen/LoginScreenCoordinator.swift | 2 +- .../SoftLogoutScreenCoordinator.swift | 2 +- .../BlockedUsersScreenViewModel.swift | 2 +- .../BugReportScreenCoordinator.swift | 2 +- .../CreateRoom/CreateRoomViewModel.swift | 2 +- .../HomeScreen/HomeScreenCoordinator.swift | 3 - .../Screens/HomeScreen/HomeScreenModels.swift | 4 - .../HomeScreen/HomeScreenViewModel.swift | 9 - .../HomeScreen/View/HomeScreenContent.swift | 5 +- .../HomeScreenSessionVerificationBanner.swift | 81 ----- .../MediaPickerScreen/CameraPicker.swift | 2 +- .../MediaPickerScreen/DocumentPicker.swift | 2 +- .../PhotoLibraryPicker.swift | 2 +- .../MediaUploadPreviewScreenViewModel.swift | 2 +- .../AnalyticsPromptScreenCoordinator.swift | 0 .../AnalyticsPromptScreenModels.swift | 0 .../AnalyticsPromptScreenViewModel.swift | 0 ...alyticsPromptScreenViewModelProtocol.swift | 0 .../View/AnalyticsPromptScreen.swift | 12 +- ...dentityConfirmationScreenCoordinator.swift | 70 ++++ .../IdentityConfirmationScreenModels.swift | 37 ++ .../IdentityConfirmationScreenViewModel.swift | 95 +++++ ...yConfirmationScreenViewModelProtocol.swift | 23 ++ .../View/IdentityConfirmationScreen.swift | 107 ++++++ .../IdentityConfirmedScreenCoordinator.swift | 61 ++++ .../IdentityConfirmedScreenModels.swift} | 13 +- .../IdentityConfirmedScreenViewModel.swift | 42 +++ ...tityConfirmedScreenViewModelProtocol.swift | 23 ++ .../View/IdentityConfirmedScreen.swift | 75 ++++ ...ficationPermissionsScreenCoordinator.swift | 59 +++ .../NotificationPermissionsScreenModels.swift | 28 ++ ...tificationPermissionsScreenViewModel.swift | 48 +++ ...onPermissionsScreenViewModelProtocol.swift | 23 ++ .../View/NotificationPermissionsScreen.swift | 77 ++++ ...SessionVerificationScreenCoordinator.swift | 11 +- .../SessionVerificationScreenModels.swift | 5 - ...essionVerificationScreenStateMachine.swift | 0 .../SessionVerificationScreenViewModel.swift | 28 +- ...nVerificationScreenViewModelProtocol.swift | 2 + .../View/SessionVerificationScreen.swift | 71 ++-- .../ReportContentScreenCoordinator.swift | 2 +- .../RoomMemberDetailsScreenViewModel.swift | 2 +- .../Style/TimelineItemPlainStylerView.swift | 3 +- .../EncryptedHistoryRoomTimelineView.swift | 68 ---- ...SecureBackupKeyBackupScreenViewModel.swift | 2 +- .../View/SecureBackupKeyBackupScreen.swift | 30 +- ...SecureBackupLogoutConfirmationScreen.swift | 57 ++- ...reBackupRecoveryKeyScreenCoordinator.swift | 4 +- .../SecureBackupRecoveryKeyScreenModels.swift | 3 + ...cureBackupRecoveryKeyScreenViewModel.swift | 8 +- .../View/SecureBackupRecoveryKeyScreen.swift | 19 +- .../SecureBackupScreenCoordinator.swift | 3 +- .../SettingsScreenCoordinator.swift | 3 - .../SettingsScreen/SettingsScreenModels.swift | 3 - .../SettingsScreenViewModel.swift | 5 - .../SettingsScreen/View/SettingsScreen.swift | 5 - .../StartChatScreenCoordinator.swift | 2 +- .../StartChatScreenViewModel.swift | 2 +- .../Manager/NotificationManager.swift | 9 +- .../Manager/NotificationManagerProtocol.swift | 1 + .../MockRoomTimelineControllerFactory.swift | 3 +- .../RoomTimelineController.swift | 65 +--- .../RoomTimelineControllerFactory.swift | 6 +- ...oomTimelineControllerFactoryProtocol.swift | 3 +- .../TimelineItems/RoomTimelineItemView.swift | 2 - .../RoomTimelineItemViewState.swift | 4 - .../UITests/UITestsAppCoordinator.swift | 10 +- .../UnitTests/UnitTestsAppCoordinator.swift | 2 +- IntegrationTests/Sources/Application.swift | 7 + IntegrationTests/Sources/Common.swift | 22 +- ...est_analyticsPromptScreen-iPad-en-GB.1.png | 4 +- ...st_analyticsPromptScreen-iPad-pseudo.1.png | 4 +- ...nalyticsPromptScreen-iPhone-15-en-GB.1.png | 4 +- ...alyticsPromptScreen-iPhone-15-pseudo.1.png | 4 +- ...SessionVerificationBanner-iPad-en-GB.1.png | 3 - ...essionVerificationBanner-iPad-pseudo.1.png | 3 - ...onVerificationBanner-iPhone-15-en-GB.1.png | 3 - ...nVerificationBanner-iPhone-15-pseudo.1.png | 3 - ...dentityConfirmationScreen-iPad-en-GB.1.png | 3 + ...entityConfirmationScreen-iPad-pseudo.1.png | 3 + ...tyConfirmationScreen-iPhone-15-en-GB.1.png | 3 + ...yConfirmationScreen-iPhone-15-pseudo.1.png | 3 + ...t_identityConfirmedScreen-iPad-en-GB.1.png | 3 + ..._identityConfirmedScreen-iPad-pseudo.1.png | 3 + ...ntityConfirmedScreen-iPhone-15-en-GB.1.png | 3 + ...tityConfirmedScreen-iPhone-15-pseudo.1.png | 3 + ...ficationPermissionsScreen-iPad-en-GB.1.png | 3 + ...icationPermissionsScreen-iPad-pseudo.1.png | 3 + ...ionPermissionsScreen-iPhone-15-en-GB.1.png | 3 + ...onPermissionsScreen-iPhone-15-pseudo.1.png | 3 + ...ackupKeyBackupScreen-iPad-en-GB.Set-up.png | 4 +- ...ckupKeyBackupScreen-iPad-pseudo.Set-up.png | 4 +- ...KeyBackupScreen-iPhone-15-en-GB.Set-up.png | 4 +- ...eyBackupScreen-iPhone-15-pseudo.Set-up.png | 4 +- ...pLogoutConfirmationScreen-iPad-en-GB.1.png | 4 +- ...LogoutConfirmationScreen-iPad-pseudo.1.png | 4 +- ...utConfirmationScreen-iPhone-15-en-GB.1.png | 4 +- ...tConfirmationScreen-iPhone-15-pseudo.1.png | 4 +- ...ecoveryKeyScreen-iPad-en-GB.Incomplete.png | 4 +- ...ecoveryKeyScreen-iPad-en-GB.Not-set-up.png | 4 +- ...kupRecoveryKeyScreen-iPad-en-GB.Set-up.png | 4 +- ...coveryKeyScreen-iPad-pseudo.Incomplete.png | 4 +- ...coveryKeyScreen-iPad-pseudo.Not-set-up.png | 4 +- ...upRecoveryKeyScreen-iPad-pseudo.Set-up.png | 4 +- ...ryKeyScreen-iPhone-15-en-GB.Incomplete.png | 4 +- ...ryKeyScreen-iPhone-15-en-GB.Not-set-up.png | 4 +- ...coveryKeyScreen-iPhone-15-en-GB.Set-up.png | 4 +- ...yKeyScreen-iPhone-15-pseudo.Incomplete.png | 4 +- ...yKeyScreen-iPhone-15-pseudo.Not-set-up.png | 4 +- ...overyKeyScreen-iPhone-15-pseudo.Set-up.png | 4 +- ...ssionVerification-iPad-en-GB.Cancelled.png | 4 +- ...sessionVerification-iPad-en-GB.Initial.png | 4 +- ...rification-iPad-en-GB.Request-Accepted.png | 4 +- ...ion-iPad-en-GB.Requesting-Verification.png | 4 +- ...ification-iPad-en-GB.Showing-Challenge.png | 4 +- ...essionVerification-iPad-en-GB.Verified.png | 4 +- ...sionVerification-iPad-pseudo.Cancelled.png | 4 +- ...essionVerification-iPad-pseudo.Initial.png | 4 +- ...ification-iPad-pseudo.Request-Accepted.png | 4 +- ...on-iPad-pseudo.Requesting-Verification.png | 4 +- ...fication-iPad-pseudo.Showing-Challenge.png | 4 +- ...ssionVerification-iPad-pseudo.Verified.png | 4 +- ...Verification-iPhone-15-en-GB.Cancelled.png | 4 +- ...onVerification-iPhone-15-en-GB.Initial.png | 4 +- ...ation-iPhone-15-en-GB.Request-Accepted.png | 4 +- ...Phone-15-en-GB.Requesting-Verification.png | 4 +- ...tion-iPhone-15-en-GB.Showing-Challenge.png | 4 +- ...nVerification-iPhone-15-en-GB.Verified.png | 4 +- ...erification-iPhone-15-pseudo.Cancelled.png | 4 +- ...nVerification-iPhone-15-pseudo.Initial.png | 4 +- ...tion-iPhone-15-pseudo.Request-Accepted.png | 4 +- ...hone-15-pseudo.Requesting-Verification.png | 4 +- ...ion-iPhone-15-pseudo.Showing-Challenge.png | 4 +- ...Verification-iPhone-15-pseudo.Verified.png | 4 +- ...nalyticsSettingsScreenViewModelTests.swift | 4 +- UnitTests/Sources/AnalyticsTests.swift | 4 +- .../AppLock/AppLockScreenViewModelTests.swift | 4 +- .../Sources/AppLock/AppLockServiceTests.swift | 4 +- ...kSetupBiometricsScreenViewModelTests.swift | 4 +- .../AppLockSetupPINScreenViewModelTests.swift | 4 +- .../Sources/AppRouteURLParserTests.swift | 2 +- .../ComposerToolbarViewModelTests.swift | 4 +- .../Sources/HomeScreenViewModelTests.swift | 2 +- .../NotificationManagerTests.swift | 2 +- ...ficationSettingsScreenViewModelTests.swift | 2 +- UnitTests/Sources/PermalinkBuilderTests.swift | 2 +- .../Sources/RoomDetailsViewModelTests.swift | 2 +- .../Sources/RoomScreenViewModelTests.swift | 2 +- .../SessionVerificationViewModelTests.swift | 5 +- changelog.d/2592.change | 1 + changelog.d/2593.feature | 1 + changelog.d/2594.change | 1 + changelog.d/2595.change | 1 + project.yml | 2 +- 176 files changed, 1886 insertions(+), 912 deletions(-) create mode 100644 ElementX/Resources/Assets.xcassets/images/background-bottom.imageset/Contents.json create mode 100644 ElementX/Resources/Assets.xcassets/images/background-bottom.imageset/background-bottom-dark.png create mode 100644 ElementX/Resources/Assets.xcassets/images/background-bottom.imageset/background-bottom-light.png create mode 100644 ElementX/Resources/Assets.xcassets/images/notifications-prompt-graphic.imageset/Alerts.svg create mode 100644 ElementX/Resources/Assets.xcassets/images/notifications-prompt-graphic.imageset/Contents.json create mode 100644 ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift rename ElementX/Sources/{Screens/AnalyticsPromptScreen/View => Other/SwiftUI/Views}/RoundedLabelItem.swift (100%) delete mode 100644 ElementX/Sources/Screens/HomeScreen/View/HomeScreenSessionVerificationBanner.swift rename ElementX/Sources/Screens/{ => Onboarding}/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift (100%) rename ElementX/Sources/Screens/{ => Onboarding}/AnalyticsPromptScreen/AnalyticsPromptScreenModels.swift (100%) rename ElementX/Sources/Screens/{ => Onboarding}/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift (100%) rename ElementX/Sources/Screens/{ => Onboarding}/AnalyticsPromptScreen/AnalyticsPromptScreenViewModelProtocol.swift (100%) rename ElementX/Sources/Screens/{ => Onboarding}/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift (92%) create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenCoordinator.swift create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenModels.swift create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenCoordinator.swift rename ElementX/Sources/{Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift => Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenModels.swift} (73%) create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/View/IdentityConfirmedScreen.swift create mode 100644 ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenCoordinator.swift create mode 100644 ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenModels.swift create mode 100644 ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/View/NotificationPermissionsScreen.swift rename ElementX/Sources/Screens/{ => Onboarding}/SessionVerificationScreen/SessionVerificationScreenCoordinator.swift (88%) rename ElementX/Sources/Screens/{ => Onboarding}/SessionVerificationScreen/SessionVerificationScreenModels.swift (97%) rename ElementX/Sources/Screens/{ => Onboarding}/SessionVerificationScreen/SessionVerificationScreenStateMachine.swift (100%) rename ElementX/Sources/Screens/{ => Onboarding}/SessionVerificationScreen/SessionVerificationScreenViewModel.swift (88%) rename ElementX/Sources/Screens/{ => Onboarding}/SessionVerificationScreen/SessionVerificationScreenViewModelProtocol.swift (97%) rename ElementX/Sources/Screens/{ => Onboarding}/SessionVerificationScreen/View/SessionVerificationScreen.swift (78%) delete mode 100644 ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift delete mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPad-en-GB.1.png delete mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPad-pseudo.1.png delete mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPhone-15-en-GB.1.png delete mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPhone-15-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPad-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPad-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPhone-15-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPhone-15-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPad-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPad-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPhone-15-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPhone-15-pseudo.1.png create mode 100644 changelog.d/2592.change create mode 100644 changelog.d/2593.feature create mode 100644 changelog.d/2594.change create mode 100644 changelog.d/2595.change diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 05fd36819..555bb361b 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ /* Begin PBXBuildFile section */ 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; }; + 01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */; }; 0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B746EFA112532A7B701FB914 /* RoomNotificationSettingsCustomSectionView.swift */; }; 01B63F1A04A276B39AC17014 /* CallInviteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */; }; 020C530986D7B97631877FEF /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */; }; @@ -36,6 +37,7 @@ 04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422724361B6555364C43281E /* RoomHeaderView.swift */; }; 04F17DE71A50206336749BAC /* UserPreferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */; }; 059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; }; + 05BAB510CBC2ED35C154ADD0 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFD012C3A9F5EF276DDD4AA /* AnalyticsPromptScreenViewModelProtocol.swift */; }; 05EC896A4B9AF4A56670C0BB /* SessionVerificationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */; }; 066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; }; 06AA515C7053FD7E17A5CF81 /* RoomNotificationSettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */; }; @@ -56,13 +58,13 @@ 09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */; }; 09EF4222EEBBA1A7B8F4071E /* VoiceMessageRecordingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E61DDB42C0DE429C0955D8 /* VoiceMessageRecordingButton.swift */; }; 0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; }; - 0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */; }; 0ACAA31FD0399CEEBA3ECC21 /* UserDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */; }; 0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; }; 0B57C2399B9E1CE5CE0D8005 /* ComposerToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D121B4FCFC38DBCC17BCC6D6 /* ComposerToolbar.swift */; }; 0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */; }; 0BE4D5CBF86956410F071F91 /* CreateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */; }; 0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; }; + 0C346A4AD174F441EDB1414E /* IdentityConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */; }; 0C47AE2CA7929CB3B0E2D793 /* ServerSelectionScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */; }; 0C58A846F61949B1D545D661 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421E716C521F96D24ECE69B3 /* NoticeRoomTimelineItem.swift */; }; 0C797CD650DFD2876BEC5173 /* CollapsibleReactionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7C6DDBB5D12F6EF6A3D6E1 /* CollapsibleReactionLayout.swift */; }; @@ -82,6 +84,7 @@ 0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */; }; 1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0376C429FAB1687C3D905F3E /* MockCoder.swift */; }; 119AE9A3FC6E0606C1146528 /* NotificationSettingsEditScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97F8963B14EB0AF3940DDBF /* NotificationSettingsEditScreenRoomCell.swift */; }; + 11A6B8E3CBDBF0A4107FF4CE /* OnboardingFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */; }; 126EE01D8BEAEF26105D83C5 /* RoomDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */; }; 12C867E85E6D12EEDFD0B127 /* CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */; }; 12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; }; @@ -149,6 +152,7 @@ 241CDEFE23819867D9B39066 /* RoomChangePermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE75941583A033A9EDC9FE0 /* RoomChangePermissionsScreenViewModel.swift */; }; 244407B18B2F2D6466BA5961 /* RoomChangeRolesScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82DFA1B7B088D033E0794B82 /* RoomChangeRolesScreenCoordinator.swift */; }; 245F7FE5961BD10C145A26E0 /* UITimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA689E792E679F5E3956F21 /* UITimelineView.swift */; }; + 24A1BBADAC43DC3F3A7347DA /* AnalyticsPromptScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */; }; 24A75F72EEB7561B82D726FD /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; }; 24B7CD41342C143117ADA768 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B1CC9AA154F4D5435BF60A /* Comparable.swift */; }; 24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; }; @@ -182,6 +186,7 @@ 2BA59D0AEFB4B82A2EC2A326 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 50009897F60FAE7D63EF5E5B /* Kingfisher */; }; 2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; }; 2BBA132149DEBED6624084A8 /* MessageForwardingScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE373A7F20780BA84B59C /* MessageForwardingScreenCoordinator.swift */; }; + 2BBE320EE426A347AAE5C7DA /* IdentityConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */; }; 2C4C750D0039AFABDF24236C /* TemplateScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */; }; 2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */; }; 2CA6ABBC9A88EB89EA52FCCB /* ConfettiScene.scn in Resources */ = {isa = PBXBuildFile; fileRef = B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */; }; @@ -250,6 +255,7 @@ 3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DC38E64A5ED3FDB201029A /* BugReportService.swift */; }; 3DAD62988F072607441CB7A5 /* PollFormScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */; }; 3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861FE915C7B5466E6962BBA /* StartChatScreen.swift */; }; + 3E7B65C2C97748D5D65AAA8B /* NotificationPermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB2FC2AA9A07EE792DF65CF /* NotificationPermissionsScreenModels.swift */; }; 3EC5A41F9FB7DD63A4DC6144 /* RoomChangeRolesScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14B1DE3E2D5D26732C49036 /* RoomChangeRolesScreenViewModel.swift */; }; 3EC698F80DDEEFA273857841 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893777A4997BBDB68079D4F5 /* ArrayTests.swift */; }; 3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; }; @@ -278,6 +284,7 @@ 44BDD670FF9095ACE240A3A2 /* VoiceMessageMediaManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */; }; 44DA28B1E1F9C97C5795F7B3 /* AppLockSetupUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A1BBEF7318CA6B6ACCF4AE /* AppLockSetupUITests.swift */; }; 44F0E1B576C7599DF8022071 /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 391D11F92DFC91666AA1503F /* SwiftOGG */; }; + 454311EAC17D778E19F46592 /* NotificationPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */; }; 454F8DDC4442C0DE54094902 /* LABiometryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F219838588C62198E726E3 /* LABiometryType.swift */; }; 4557192F5B15A8D9BB920232 /* AdvancedSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E492690C8B27A892C194CC4 /* AdvancedSettingsScreenCoordinator.swift */; }; 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; @@ -292,13 +299,14 @@ 47FF70C051A991FB65CDBCF3 /* RoomScreenInteractionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0135A608FFAD86E6674EE730 /* RoomScreenInteractionHandler.swift */; }; 4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */; }; 484202C5D50983442D24D061 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; }; + 489BB6A733D3DA0FE7062650 /* IdentityConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */; }; 491D62ACD19E6F134B1766AF /* RoomNotificationSettingsUserDefinedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */; }; 492274DA6691EE985C2FCCAA /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; }; 4940B439681767BE9D78CFDB /* AppLockSetupBiometricsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F5EE5DE3B55D59299DB5BC /* AppLockSetupBiometricsScreenViewModelTests.swift */; }; - 496CC9D59ACFAB84FD9B3B5F /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */; }; 49814A48470F347426513B07 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */; }; 49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; }; 4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; }; + 4A8287E5281B44A8754BE509 /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */; }; 4A85928E27D4C1A548A06EE9 /* StartChatScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */; }; 4A9CEEE612D6D8B3DDBD28BA /* RoomListFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24EC819497BB5F8C4998D760 /* RoomListFilterView.swift */; }; 4AAA8606FBA290E23D15422E /* AvatarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */; }; @@ -326,6 +334,7 @@ 50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD0FF64B0E6470F66F42E182 /* EstimatedWaveformView.swift */; }; 50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542D4F49FABA056DEEEB3400 /* RustTracing.swift */; }; 5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */; }; + 5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */; }; 518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */; }; 51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; }; 520EEDAFBC778AB0B41F2F53 /* ClientMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE6170EFE6A161B0A68AB61 /* ClientMock.swift */; }; @@ -349,9 +358,8 @@ 565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; }; 56BAB81A0D03C2EF09B86294 /* ComposerToolbarModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA188BB0A216D763E46E3279 /* ComposerToolbarModels.swift */; }; 56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; }; - 5770C4906668C6D3008A2AC9 /* SessionVerificationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5046BB295AEAFA6FB81655 /* SessionVerificationScreenModels.swift */; }; + 5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */; }; 5780E444F405AA1304E1C23E /* DeveloperOptionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */; }; - 584590D0EA548152A393E72C /* HomeScreenSessionVerificationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */; }; 588411C8FD72B2A2DFE5F7DE /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E992D7B8BE54B2AB454613AF /* XCUIElement.swift */; }; 5894C2514400A4FBC9327632 /* ServerConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03277E40D0E0DE0712021A71 /* ServerConfirmationScreenCoordinator.swift */; }; 5897A59DDBD3592282092223 /* MediaSourceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49B9785E3AD7D1C15A29F2F /* MediaSourceProxy.swift */; }; @@ -375,8 +383,8 @@ 5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */; }; 5EE1D4E316D66943E97FDCF2 /* BloomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BEB970F500BFB248443FA1 /* BloomView.swift */; }; 5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; }; - 5F28C9146694B381BB82E18C /* AnalyticsPromptScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B65A314DF40B6BBF775C2BC /* AnalyticsPromptScreenCoordinator.swift */; }; 5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; }; + 601AB75BD52B0B4276CEB84A /* SessionVerificationScreenStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */; }; 60ED66E63A169E47489348A8 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; }; 6146996D5C4DDD5DA816FC87 /* AuthenticationTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCACD75595C40EACD6AD4A74 /* AuthenticationTextFieldStyle.swift */; }; 617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; }; @@ -435,6 +443,7 @@ 6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; }; 6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; }; 6D6E651ACACE27E9C5690818 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE47A97726F0675DEE387BF9 /* TypingIndicatorView.swift */; }; + 6DC8E43BA04AC2AC4EB2EB97 /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */; }; 6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */; }; 6E4E401BE97AC241DA7C7716 /* AppLockSetupSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 502F986D57158674172C58E3 /* AppLockSetupSettingsScreenModels.swift */; }; 6E63704717F17593A475D152 /* RoomNotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA14564EE143F73F7E4D1F79 /* RoomNotificationSettingsScreenModels.swift */; }; @@ -449,6 +458,7 @@ 70558528EF68CAAEF09972D5 /* RoomTimelineItemFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96ED747FF90332EA1333C22 /* RoomTimelineItemFixtures.swift */; }; 706289B086B0A6B0C211763F /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; }; 706F79A39BDB32F592B8C2C7 /* UIKitBackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */; }; + 707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */; }; 70B83D44043293B4B77440B9 /* PollFormScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB63761D9F9CE8B23CBD6179 /* PollFormScreenModels.swift */; }; 719E7AAD1F8E68F68F30FECD /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40C19719687984FD9478FBE /* Task.swift */; }; 71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */; }; @@ -466,6 +476,7 @@ 755395927DDD6EBDDA5E217A /* SettingsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */; }; 755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; }; 762DAF94846C7AC8550F1CC1 /* MediaPlayerProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E23D8EE6CBACF32F1EC874 /* MediaPlayerProviderProtocol.swift */; }; + 762DB0973865293F0C3D3D7B /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */; }; 763D69741D58D2B650BC1FC9 /* CallScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37FA1A5D55633E1942B153B /* CallScreenCoordinator.swift */; }; 7640A4B412CACF15D143CCD4 /* Strings+SAS.swift in Sources */ = {isa = PBXBuildFile; fileRef = B172057567E049007A5C4D92 /* Strings+SAS.swift */; }; 764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EEA67A6796BDC2761619C5 /* PaginationIndicatorRoomTimelineView.swift */; }; @@ -474,6 +485,7 @@ 76BA28216FBAF83B2D86A027 /* InvitesScreenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2A71915C1F075E403F559C /* InvitesScreenCell.swift */; }; 7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */; }; 7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */; }; + 77574A519A4E484880053EAD /* IdentityConfirmationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FDF541AE914059942B575B4 /* IdentityConfirmationScreenModels.swift */; }; 77693820498ABF3508814D49 /* AppLockServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97F9661ABF08CE002054A2 /* AppLockServiceTests.swift */; }; 77920AFA8091AC6B9F190C90 /* Signposter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */; }; 77BB228AEA861E50FFD6A228 /* HomeScreenEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FEA560929DD73FFEF8C3DF /* HomeScreenEmptyStateView.swift */; }; @@ -485,7 +497,6 @@ 795A854F63301DC6B46217B9 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; }; 79741C1953269FF1A211D246 /* RoomPollsHistoryScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E14FF533D25A0692F7CEB0 /* RoomPollsHistoryScreenViewModel.swift */; }; 7A02EB29F3B993AB20E0A198 /* RoomPollsHistoryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C8C368A611B9CB79C7F5FA /* RoomPollsHistoryScreen.swift */; }; - 7A0A0929556792FB19B812C5 /* SessionVerificationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */; }; 7A170A5A4A352954BB2A1B96 /* AuthenticationStartScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E8C8817F59BEC7E358EB78 /* AuthenticationStartScreen.swift */; }; 7A642EE5F1ADC5D520F21924 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; }; 7A71AEF419904209BB8C2833 /* UserAgentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */; }; @@ -598,6 +609,7 @@ 934051B17A884AB0635DF81B /* BlockedUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */; }; 93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; }; 93A549135E6C027A0D823BFE /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 593FBBF394712F2963E98A0B /* DTCoreText */; }; + 93AC1E8418D8C827671FB3A9 /* IdentityConfirmedScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 595EC503DA5517BBE6D39406 /* IdentityConfirmedScreenCoordinator.swift */; }; 93BA4A81B6D893271101F9F0 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; }; 93BAF04D9CCBC0A8841414D0 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */; }; 9408CE8B8865C0C8DD4C9869 /* NoticeRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD51B4D5173F7FC886F5360 /* NoticeRoomTimelineItemContent.swift */; }; @@ -619,7 +631,6 @@ 988BA75A182738150894A23F /* UserIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AE4B3273BA189FDCD4055C /* UserIndicator.swift */; }; 9912F9EB2D6589141A2957B4 /* AppLockScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C44BBC892499BE45B074F89 /* AppLockScreenCoordinator.swift */; }; 992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; }; - 9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */; }; 99ED42B8F8D6BFB1DBCF4C45 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = D661CAB418C075A94306A792 /* AnalyticsEvents */; }; 9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; 9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */; }; @@ -632,6 +643,7 @@ 9BB91CABB10D8FE90C491BCD /* StaticLocationScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */; }; 9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F2402D738694F98729A441 /* RoomTimelineProvider.swift */; }; 9BEA56957B3AF954E7321658 /* ComposerToolbarViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44928D844E16EE48A311FCA /* ComposerToolbarViewModelProtocol.swift */; }; + 9C4EC28A921486B1775D7F8C /* IdentityConfirmedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */; }; 9C55746D8F6A3E35CFCF4A7A /* AuthenticationStartLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 598F01EBD0C4CC550C644418 /* AuthenticationStartLogo.swift */; }; 9C5A07E7C33F3F40287D7861 /* SettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */; }; 9D2E03DB175A6AB14589076D /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; }; @@ -641,7 +653,6 @@ 9DD5AA10E85137140FEA86A3 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; }; 9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */; }; 9DE801D278AC34737467F937 /* VoiceMessageMediaManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */; }; - 9DF3F6318A4402305F5EB869 /* AnalyticsPromptScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */; }; 9E838A62918E47BC72D6640D /* UserIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB54B4F94686CCF0289B72F /* UserIndicatorPresenter.swift */; }; 9EBDC79CAC9B63A0D626E333 /* LegalInformationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */; }; 9F11E743EA01482E78A438B0 /* GlobalSearchScreenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22DB19219E6CC4D002E15D48 /* GlobalSearchScreenCell.swift */; }; @@ -704,6 +715,7 @@ AA050DF4AEE54A641BA7CA22 /* RoomSummaryProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CC626F97AD70FF0420C115 /* RoomSummaryProviderProtocol.swift */; }; AA5924D3B67F7ACD98BBEFDC /* OrientationManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4756240773D26AB74C22668 /* OrientationManagerProtocol.swift */; }; AA93B3F9B5DD097DEF79F981 /* NotificationSettingsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */; }; + AADE7C2497A7B55D8BED7BD6 /* IdentityConfirmedScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8319173DD66C07F45DC48848 /* IdentityConfirmedScreenViewModelProtocol.swift */; }; AAF0BBED840DF4A53EE85E77 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = C2C69B8BA5A9702E7A8BC08F /* MatrixRustSDK */; }; ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; }; AC69B6DF15FC451AB2945036 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */; }; @@ -722,7 +734,6 @@ AFA1F2543DFF7B45DF68ACD6 /* CompletionSuggestionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170BF6F7923A5C3792442F27 /* CompletionSuggestionModels.swift */; }; B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */; }; B064D42BA087649ACAE462E8 /* SoftLogoutUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */; }; - B09DC6E3D0EE87C4D4ABFAB3 /* EncryptedHistoryRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */; }; B0CB16349B96262AA65A04AF /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; }; B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; }; B13774779EA19FDD7A35A4A8 /* RoomRolesAndPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */; }; @@ -731,7 +742,6 @@ B188D0907A4D38AAAF6FEFA8 /* AppLockSetupFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */; }; B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */; }; B245583C63F8F90357B87FAE /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = A2AE110B053B55E38F8D10C7 /* KZFileWatchers */; }; - B27D3190784F85916DA1C394 /* SessionVerificationScreenStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */; }; B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */; }; B3D652AA1654270742072FB3 /* DeveloperOptionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */; }; B3EDDEC1839BB5A3747624BB /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1CCDEE545CB6453B084BF /* FormButtonStyles.swift */; }; @@ -779,6 +789,7 @@ BB9B800C6094E34860E89DC5 /* AppLockSetupBiometricsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CCF9A924521DECA44778C4 /* AppLockSetupBiometricsScreen.swift */; }; BCC864190651B3A3CF51E4DF /* MediaFileHandleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */; }; BD0BE20DBCE31253AE4490A1 /* RoomListFiltersEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC1DDB2293A51EA4C2739351 /* RoomListFiltersEmptyStateView.swift */; }; + BD11E639CF566A9DA8FCA717 /* RoundedLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE7C80EF77AD102053D3646E /* RoundedLabelItem.swift */; }; BD203FC6A7AE7637EA003643 /* RoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABDE6F66532CBEB0E016F94 /* RoomProxyMock.swift */; }; BD6685592716CA957D7BAAC4 /* RoomChangeRolesScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9B45D584D232CB9E5C7734 /* RoomChangeRolesScreenSelectedItem.swift */; }; BD6D98676111DA8FC2BE4908 /* InvitesScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86873A768B13069BB5CAECF6 /* InvitesScreenViewModelProtocol.swift */; }; @@ -804,6 +815,7 @@ C32765D740C81AD4C42E8F50 /* CreateRoomFlowParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 935C2FB18EFB8EEE96B26330 /* CreateRoomFlowParameters.swift */; }; C3317EF833AB4060988DF098 /* SAS.strings in Resources */ = {isa = PBXBuildFile; fileRef = 135FC689EA39AE1D34153B58 /* SAS.strings */; }; C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */; }; + C3BB6887CF13B19182E81F87 /* IdentityConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */; }; C4078364FD9FA00EA9D00A15 /* RoomMembersListScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CDF9A107BFE6C79B58D6B5 /* RoomMembersListScreenViewModelProtocol.swift */; }; C413D36D44F89DE63D3ADFA4 /* ReportContentScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A433BE28B40D418237BE37B5 /* ReportContentScreen.swift */; }; C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9873076F224E4CE09D8BD47D /* TemplateScreenUITests.swift */; }; @@ -823,6 +835,7 @@ C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68010886142843705E342645 /* ProgressMaskModifier.swift */; }; C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; }; C85C7A201E4CFDA477ACEBEB /* AppLockSetupSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */; }; + C8A9C595038AFA2D707AC8C1 /* NotificationPermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */; }; C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; }; C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; }; C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; }; @@ -855,6 +868,7 @@ CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */; }; CFEC53440C572CEEABC4A6A0 /* ElementXAttributeScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */; }; D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; }; + D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */; }; D050D7756E92CA061ED0ABF0 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */; }; D0550B8E0AE2C0CDBE52C88F /* MediaPlayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE87C931165F5E201CACBB87 /* MediaPlayerProtocol.swift */; }; D0A965852D6C04138FA55181 /* SecureBackupLogoutConfirmationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */; }; @@ -865,6 +879,7 @@ D1E29F345F1220E1AF1BE9DF /* ReadReceiptsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0A77874B29D79DDFC051AC /* ReadReceiptsSummaryView.swift */; }; D1EEF0CB0F5D9C15E224E670 /* landscape_test_video.mov in Resources */ = {isa = PBXBuildFile; fileRef = 9A2AC7BE17C05CF7D2A22338 /* landscape_test_video.mov */; }; D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */; }; + D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBE70FFB7936F35811772C1 /* IdentityConfirmedScreenModels.swift */; }; D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */; }; D29E046C1E3045E0346C479D /* RoomRolesAndPermissionsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45571C2EBD98ED7E0CEA7AF7 /* RoomRolesAndPermissionsUITests.swift */; }; D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; }; @@ -874,8 +889,8 @@ D415764645491F10344FC6AC /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F18AECC9D38C2B6D85F99C /* Publisher.swift */; }; D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.swift */; }; D46C33F8B61B55F0C8C2D15F /* LocationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */; }; - D4ACF3276F5D0DA28D4028C9 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */; }; D4D5595C4A2A702CFF4E94FF /* HeroImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC2F1622C5BBABED6012E12 /* HeroImage.swift */; }; + D4D7CCECC6C0AAFC42E165BB /* NotificationPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */; }; D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */; }; D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */; }; D5681C80D8281560AACE0035 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045253F9967A535EE5B16691 /* Label.swift */; }; @@ -887,17 +902,18 @@ D6661A94DBD97658B2ADBD6A /* MapTilerStaticMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */; }; D7CDBAE82782BD0529DECB5F /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; }; D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; }; - D8385A51A3D0FA9283556281 /* RoundedLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */; }; D876EC0FED3B6D46C806912A /* AvatarSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */; }; D9473FC9B077A6EDB7A12001 /* LocationRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */; }; D98B5EE8C4F5A2CE84687AE8 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; }; D9F80CE61BF8FF627FDB0543 /* LoadableImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352359663A0E52BA20761EE /* LoadableImage.swift */; }; DA7E867F5EAFF8E20B2EE3B6 /* SecureBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.swift */; }; DB079D1929B5A5F52D207C83 /* RoomDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */; }; + DB65401349C143DFF883E2B0 /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8EC6EA7EDFCE46710DA306 /* AnalyticsPromptScreenViewModel.swift */; }; DBC8D1DBFE9F9CA7662BC8AA /* RoomPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */; }; DC08ADC41E792086A340A8B3 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; }; DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */; }; DC77E9DB2CFBE84A2BDF20C5 /* RoomRolesAndPermissionsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0833F51229E166BCA141D004 /* RoomRolesAndPermissionsFlowCoordinator.swift */; }; + DCFE7CB3B9A104330BBB96AD /* AnalyticsPromptScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */; }; DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; }; DDFBDEE1DC32BDD5488F898C /* ClientProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */; }; DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; }; @@ -932,7 +948,6 @@ E4B07FF075C99D04D9AF792D /* AppLockSetupPINScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B410B32B72C90BF94E481F33 /* AppLockSetupPINScreenModels.swift */; }; E4BAEED438A843D7B01D8069 /* CompletionSuggestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F421E51DF00377DE1A01354 /* CompletionSuggestionView.swift */; }; E4F924DECC66389C1C810550 /* AuthenticationStartScreenBackgroundImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D685B4DB38BB5BD87C956A /* AuthenticationStartScreenBackgroundImage.swift */; }; - E570117376826665640F0CFD /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */; }; E58F1F3276E98A93F7D39219 /* RoomPollsHistoryScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D8479BB704B7EF696F8ABE /* RoomPollsHistoryScreenCoordinator.swift */; }; E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */; }; E62EC30B39354A391E32A126 /* AudioRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2D505742FDA21FCDC4C18A /* AudioRoomTimelineView.swift */; }; @@ -950,7 +965,6 @@ E9347F56CF0683208F4D9249 /* RoomNotificationSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */; }; E9560744F7B0292E20ECE5F2 /* RoomDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */; }; E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */; }; - E9F148072F9513EC2272AA21 /* SessionVerificationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */; }; EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; }; EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */; }; EA78A7512AFB1E5451744EB1 /* AppRouteURLParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */; }; @@ -997,7 +1011,6 @@ F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */; }; F40B097470D3110DFDB1FAAA /* LegalInformationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */; }; F421FD5979EF53C8204BDC77 /* SecureBackupLogoutConfirmationScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC09F30B0E1010951952BDC /* SecureBackupLogoutConfirmationScreenUITests.swift */; }; - F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */; }; F4971845B5C4F270F6BC5745 /* ScaledFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D82F234B3576BD6268C7950 /* ScaledFrameModifier.swift */; }; F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */; }; F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; }; @@ -1104,6 +1117,7 @@ /* Begin PBXFileReference section */ 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = ""; }; + 00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreen.swift; sourceTree = ""; }; 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelTests.swift; sourceTree = ""; }; 0135A608FFAD86E6674EE730 /* RoomScreenInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenInteractionHandler.swift; sourceTree = ""; }; 01B795AAAB7B8747FE2FF311 /* LogViewerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenModels.swift; sourceTree = ""; }; @@ -1193,10 +1207,12 @@ 15A657D96779D1DEB8EF1327 /* CreateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModel.swift; sourceTree = ""; }; 15F30E7AE8A303E8FEC2499E /* ReadReceiptCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceiptCell.swift; sourceTree = ""; }; 16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenUITests.swift; sourceTree = ""; }; + 161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenStateMachine.swift; sourceTree = ""; }; 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenModels.swift; sourceTree = ""; }; 170BF6F7923A5C3792442F27 /* CompletionSuggestionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionModels.swift; sourceTree = ""; }; 1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = ""; }; 1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfigurationTests.swift; sourceTree = ""; }; + 18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = ""; }; 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = ""; }; 1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReadReceiptsView.swift; sourceTree = ""; }; 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxy.swift; sourceTree = ""; }; @@ -1210,7 +1226,6 @@ 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = ""; }; 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModel.swift; sourceTree = ""; }; 1ABDE6F66532CBEB0E016F94 /* RoomProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyMock.swift; sourceTree = ""; }; - 1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenStateMachine.swift; sourceTree = ""; }; 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItem.swift; sourceTree = ""; }; 1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillAttachmentViewProvider.swift; sourceTree = ""; }; 1B564D748B67A156F413CD97 /* NotificationSettingsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenModels.swift; sourceTree = ""; }; @@ -1227,6 +1242,7 @@ 1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; 1D8866FE1CCCF10305FCACBC /* CallScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenUITests.swift; sourceTree = ""; }; 1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenViewModel.swift; sourceTree = ""; }; + 1DB2FC2AA9A07EE792DF65CF /* NotificationPermissionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenModels.swift; sourceTree = ""; }; 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = ""; }; 1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenModels.swift; sourceTree = ""; }; 1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1238,6 +1254,7 @@ 1FD51B4D5173F7FC886F5360 /* NoticeRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItemContent.swift; sourceTree = ""; }; 201305507D7DFD16E544563A /* EmojiLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLoaderProtocol.swift; sourceTree = ""; }; 20872C3887F835958CE2F1D0 /* MapTilerStaticMapProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMapProtocol.swift; sourceTree = ""; }; + 20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModelProtocol.swift; sourceTree = ""; }; 2141693488CE5446BB391964 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItemContent.swift; sourceTree = ""; }; 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = ""; }; @@ -1282,7 +1299,6 @@ 28C19F54A0C4FC9AB7ABD583 /* TextRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItemContent.swift; sourceTree = ""; }; 295E28C3B9EAADF519BF2F44 /* AuthenticationFlowCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationFlowCoordinatorUITests.swift; sourceTree = ""; }; 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = ""; }; - 2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = ""; }; 2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = ""; }; 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenModels.swift; sourceTree = ""; }; @@ -1301,6 +1317,7 @@ 2F36C5D9B37E50915ECBD3EE /* RoomMemberProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxy.swift; sourceTree = ""; }; 2FD0E68C42CA7DDCD4CAD68D /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = ""; }; 303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModel.swift; sourceTree = ""; }; + 307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreen.swift; sourceTree = ""; }; 309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriver.swift; sourceTree = ""; }; 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerTests.swift; sourceTree = ""; }; 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceipt.swift; sourceTree = ""; }; @@ -1352,6 +1369,7 @@ 3D65BCC659FD9087E49B3C25 /* AppAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearance.swift; sourceTree = ""; }; 3D9B45D584D232CB9E5C7734 /* RoomChangeRolesScreenSelectedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenSelectedItem.swift; sourceTree = ""; }; 3D9FCE4D1E3A81AC1CC5CB91 /* AppLockSetupSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenCoordinator.swift; sourceTree = ""; }; + 3DBE70FFB7936F35811772C1 /* IdentityConfirmedScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenModels.swift; sourceTree = ""; }; 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = ""; }; 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskService.swift; sourceTree = ""; }; 3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = ""; }; @@ -1388,6 +1406,7 @@ 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterProtocol.swift; sourceTree = ""; }; 466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenModels.swift; sourceTree = ""; }; 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = ""; }; + 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreen.swift; sourceTree = ""; }; 46D560DDA3B20C82766ACFAD /* NotificationSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenViewModel.swift; sourceTree = ""; }; 46F52419AEEDA2C006CB7181 /* NotificationSettingsEditScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenUITests.swift; sourceTree = ""; }; 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; @@ -1412,7 +1431,6 @@ 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = ""; }; 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = ""; }; 4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = ""; }; - 4B5046BB295AEAFA6FB81655 /* SessionVerificationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenModels.swift; sourceTree = ""; }; 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = ""; }; 4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; @@ -1462,6 +1480,7 @@ 58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelTests.swift; sourceTree = ""; }; 58DCB219D7B7B0299358FF81 /* SecureBackupKeyBackupScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenUITests.swift; sourceTree = ""; }; 592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreStaticMapView.swift; sourceTree = ""; }; + 595EC503DA5517BBE6D39406 /* IdentityConfirmedScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenCoordinator.swift; sourceTree = ""; }; 596AA8843AC1A234F3387767 /* SecureBackupRecoveryKeyScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenCoordinator.swift; sourceTree = ""; }; 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenUITests.swift; sourceTree = ""; }; 598F01EBD0C4CC550C644418 /* AuthenticationStartLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartLogo.swift; sourceTree = ""; }; @@ -1479,7 +1498,7 @@ 5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenCoordinator.swift; sourceTree = ""; }; 5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = ""; }; 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = ""; }; - 5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = ""; }; + 5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = ""; }; 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = ""; }; 60F18AECC9D38C2B6D85F99C /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; 612EF972F2A1800682D32C5E /* StickerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRoomTimelineView.swift; sourceTree = ""; }; @@ -1491,7 +1510,6 @@ 62B07B296D7A9D2F09120853 /* OrderedSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedSet.swift; sourceTree = ""; }; 638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveQuickLook.swift; sourceTree = ""; }; 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = ""; }; - 63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModel.swift; sourceTree = ""; }; 63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModelProtocol.swift; sourceTree = ""; }; 645E027C112740573D27765C /* SecureBackupRecoveryKeyScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenModels.swift; sourceTree = ""; }; 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridableAvatarImage.swift; sourceTree = ""; }; @@ -1522,6 +1540,7 @@ 6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreen.swift; sourceTree = ""; }; 6B5E29E9A22F45534FBD5B58 /* EmojiPickerScreenHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenHeaderView.swift; sourceTree = ""; }; 6C113E0CB7E15E9765B1817A /* EmojiProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiProvider.swift; sourceTree = ""; }; + 6C8EC6EA7EDFCE46710DA306 /* AnalyticsPromptScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModel.swift; sourceTree = ""; }; 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsScreenIdentifier.swift; sourceTree = ""; }; 6D0A27607AB09784C8501B5C /* DeveloperOptionsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelTests.swift; sourceTree = ""; }; 6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationUITests.swift; sourceTree = ""; }; @@ -1550,7 +1569,6 @@ 7310D8DFE01AF45F0689C3AA /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportFlowCoordinator.swift; sourceTree = ""; }; 7447C0AD7EF302CD027D6230 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SAS.strings; sourceTree = ""; }; - 745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabelItem.swift; sourceTree = ""; }; 74611A4182DCF5F4D42696EC /* XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = ""; }; 7463464054DDF194C54F0B04 /* LogViewerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModelProtocol.swift; sourceTree = ""; }; 74653BE903970C0E36867D46 /* GlobalSearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenCoordinator.swift; sourceTree = ""; }; @@ -1561,7 +1579,6 @@ 74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelProtocol.swift; sourceTree = ""; }; 7509AB72755DCC4B4E721B36 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/SAS.strings; sourceTree = ""; }; 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signposter.swift; sourceTree = ""; }; - 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineView.swift; sourceTree = ""; }; 76310030C831D4610A705603 /* URLComponentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponentsTests.swift; sourceTree = ""; }; 772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = ""; }; 7773CBFDBD458E0B7E270507 /* PillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillView.swift; sourceTree = ""; }; @@ -1569,11 +1586,14 @@ 78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModelProtocol.swift; sourceTree = ""; }; 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitCoordinatorTests.swift; sourceTree = ""; }; 7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerMock.swift; sourceTree = ""; }; + 796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = ""; }; + 7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenCoordinator.swift; sourceTree = ""; }; 7A5D2323D7B6BF4913EB7EED /* landscape_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = landscape_test_image.jpg; sourceTree = ""; }; 7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelProtocol.swift; sourceTree = ""; }; 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModelTests.swift; sourceTree = ""; }; 7AE094FCB6387D268C436161 /* SecureBackupScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModel.swift; sourceTree = ""; }; 7AE75941583A033A9EDC9FE0 /* RoomChangePermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenViewModel.swift; sourceTree = ""; }; + 7AFD012C3A9F5EF276DDD4AA /* AnalyticsPromptScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModelProtocol.swift; sourceTree = ""; }; 7B04BD3874D736127A8156B8 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = ""; }; 7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunner.swift; sourceTree = ""; }; @@ -1582,6 +1602,7 @@ 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinatorProtocol.swift; sourceTree = ""; }; 7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenViewModel.swift; sourceTree = ""; }; 7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenCoordinator.swift; sourceTree = ""; }; + 7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModelProtocol.swift; sourceTree = ""; }; 7CA3F8E905DF50BF22ECC18F /* ThreadDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDecorator.swift; sourceTree = ""; }; 7D0CBC76C80E04345E11F2DB /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactoryProtocol.swift; sourceTree = ""; }; @@ -1593,22 +1614,21 @@ 7EC2F1622C5BBABED6012E12 /* HeroImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeroImage.swift; sourceTree = ""; }; 7F615A00DB223FF3280204D2 /* UserDiscoveryServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoveryServiceProtocol.swift; sourceTree = ""; }; 7FB2253D36E81E045E1CB432 /* Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = ""; }; + 7FDF541AE914059942B575B4 /* IdentityConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenModels.swift; sourceTree = ""; }; 80C4927D09099497233E9980 /* WaitlistScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreen.swift; sourceTree = ""; }; 80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageEventStringBuilder.swift; sourceTree = ""; }; 818695BED971753243FEF897 /* StickerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRoomTimelineItem.swift; sourceTree = ""; }; - 8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModelProtocol.swift; sourceTree = ""; }; 81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModel.swift; sourceTree = ""; }; 81B17B1F29448D1B9049B11C /* ReportContentScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModel.swift; sourceTree = ""; }; 81B17DB1BC3B0C62AF84D230 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8296D6FB451E25CEC0767BBA /* RoomNotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenCoordinator.swift; sourceTree = ""; }; 82B612853BFB68373249777B /* SecureBackupKeyBackupScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenViewModel.swift; sourceTree = ""; }; 82DFA1B7B088D033E0794B82 /* RoomChangeRolesScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenCoordinator.swift; sourceTree = ""; }; + 8319173DD66C07F45DC48848 /* IdentityConfirmedScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenViewModelProtocol.swift; sourceTree = ""; }; 837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModel.swift; sourceTree = ""; }; 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCornerShape.swift; sourceTree = ""; }; - 840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = ""; }; 84311D707B09854D67F78BBF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersViewModelTests.swift; sourceTree = ""; }; - 84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = ""; }; 848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelTests.swift; sourceTree = ""; }; 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreen.swift; sourceTree = ""; }; 84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModel.swift; sourceTree = ""; }; @@ -1661,6 +1681,7 @@ 90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = ""; }; 913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachmentData.swift; sourceTree = ""; }; 91831D7042EADD0CC2B5EC36 /* SecureBackupScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenUITests.swift; sourceTree = ""; }; + 91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenCoordinator.swift; sourceTree = ""; }; 91CF6F7D08228D16BA69B63B /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = ""; }; 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCAuthenticationPresenter.swift; sourceTree = ""; }; 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTask.swift; sourceTree = ""; }; @@ -1691,12 +1712,13 @@ 9A2AC7BE17C05CF7D2A22338 /* landscape_test_video.mov */ = {isa = PBXFileReference; lastKnownFileType = video.quicktime; path = landscape_test_video.mov; sourceTree = ""; }; 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceProtocol.swift; sourceTree = ""; }; 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreen.swift; sourceTree = ""; }; - 9B65A314DF40B6BBF775C2BC /* AnalyticsPromptScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenCoordinator.swift; sourceTree = ""; }; 9B663BE498BB39EADC24025D /* SettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenModels.swift; sourceTree = ""; }; + 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenCoordinator.swift; sourceTree = ""; }; 9B7D8D3638864B7482E148CC /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 9C3ACC093F88FD9888518561 /* AuthenticationStartScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModel.swift; sourceTree = ""; }; 9C4048041C1A6B20CB97FD18 /* TestMeasurementParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMeasurementParser.swift; sourceTree = ""; }; 9C5E81214D27A6B898FC397D /* ElementX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = ""; }; + 9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreenViewModel.swift; sourceTree = ""; }; 9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackCoordinatorTests.swift; sourceTree = ""; }; 9C7F7DE62D33C6A26CBFCD72 /* IntegrationTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = ""; }; @@ -1733,6 +1755,7 @@ A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = ""; }; A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; + A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = ""; }; A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenCoordinator.swift; sourceTree = ""; }; A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = ""; }; A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = ""; }; @@ -1763,7 +1786,6 @@ AD9CB3B9DFA353AB2B7CD9F8 /* NotificationSettingsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenCoordinator.swift; sourceTree = ""; }; ADB35E2DB4EFE8E6F3959629 /* InviteUsersScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenUITests.swift; sourceTree = ""; }; ADCB8A232D3A8FB3E16A7303 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; - ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = ""; }; ADE6170EFE6A161B0A68AB61 /* ClientMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientMock.swift; sourceTree = ""; }; AE203026B9AD3DB412439866 /* MediaUploadingPreprocessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadingPreprocessorTests.swift; sourceTree = ""; }; AE40D4A5DD857AC16EED945A /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = ""; }; @@ -1773,12 +1795,12 @@ AF042B0FB2EE88977C91E330 /* portrait_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = portrait_test_image.jpg; sourceTree = ""; }; AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptUITests.swift; sourceTree = ""; }; AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderTests.swift; sourceTree = ""; }; + AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenModels.swift; sourceTree = ""; }; AFEF489B8E2450E2BA1A314E /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/SAS.strings; sourceTree = ""; }; B0A307A44F952CD73E63AE31 /* RoomEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomEventStringBuilder.swift; sourceTree = ""; }; B0BA67B3E4EF9D29D14A78CE /* AppLockSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSettingsScreenViewModelTests.swift; sourceTree = ""; }; B14B1DE3E2D5D26732C49036 /* RoomChangeRolesScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModel.swift; sourceTree = ""; }; B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineItem.swift; sourceTree = ""; }; - B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = ""; }; B172057567E049007A5C4D92 /* Strings+SAS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+SAS.swift"; sourceTree = ""; }; B1E227F34BE43B08E098796E /* TestablePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreview.swift; sourceTree = ""; }; B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = ""; }; @@ -1833,6 +1855,7 @@ BCF54536699ACEE3DB6BA3CB /* CompletionSuggestionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionService.swift; sourceTree = ""; }; BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = ""; }; BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReplyDetails.swift; sourceTree = ""; }; + BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModel.swift; sourceTree = ""; }; BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenCoordinator.swift; sourceTree = ""; }; BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = ""; }; BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderScreenCoordinator.swift; sourceTree = ""; }; @@ -1855,6 +1878,7 @@ C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = ""; }; C2E9B841EE4878283ECDB554 /* InviteUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreen.swift; sourceTree = ""; }; C2F079B5DBD0D85FEA687AAE /* SDKGeneratedMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKGeneratedMocks.swift; sourceTree = ""; }; + C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFlowCoordinator.swift; sourceTree = ""; }; C352359663A0E52BA20761EE /* LoadableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableImage.swift; sourceTree = ""; }; C4756240773D26AB74C22668 /* OrientationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationManagerProtocol.swift; sourceTree = ""; }; C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1911,7 +1935,6 @@ CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = ""; }; CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; - D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModelProtocol.swift; sourceTree = ""; }; D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; @@ -1964,6 +1987,7 @@ DC528B3764E3CF7FCFEF40E7 /* PollInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollInteractionHandler.swift; sourceTree = ""; }; DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenModels.swift; sourceTree = ""; }; DD97F9661ABF08CE002054A2 /* AppLockServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceTests.swift; sourceTree = ""; }; + DE7C80EF77AD102053D3646E /* RoundedLabelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabelItem.swift; sourceTree = ""; }; DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenUITests.swift; sourceTree = ""; }; DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = ""; }; DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = ""; }; @@ -1992,6 +2016,7 @@ E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteURLParserTests.swift; sourceTree = ""; }; E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = ""; }; E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = ""; }; + E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = ""; }; E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = ""; }; E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -2000,7 +2025,6 @@ E6E6BDF9D26DB05C88901416 /* RedactedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineItem.swift; sourceTree = ""; }; E6F5D66F158A6662F953733E /* NotificationSettingsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxy.swift; sourceTree = ""; }; E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactory.swift; sourceTree = ""; }; - E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenSessionVerificationBanner.swift; sourceTree = ""; }; E76A706B3EEA32B882DA5E2D /* BlockedUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenViewModelProtocol.swift; sourceTree = ""; }; E78FC546F28E045A560F2963 /* EncryptionKeyProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProviderProtocol.swift; sourceTree = ""; }; E8294DB9E95C0C0630418466 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; @@ -2017,6 +2041,7 @@ EA880E78AF4BD24E45A7808C /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/InfoPlist.strings; sourceTree = ""; }; EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentBuilderTests.swift; sourceTree = ""; }; EB63761D9F9CE8B23CBD6179 /* PollFormScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenModels.swift; sourceTree = ""; }; + EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModel.swift; sourceTree = ""; }; EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = ""; }; EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = ""; }; ECB08484CD5D77C9BF97AA78 /* WaitlistScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenUITests.swift; sourceTree = ""; }; @@ -2025,6 +2050,7 @@ ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfiguration.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; + ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = ""; }; ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; @@ -2255,15 +2281,6 @@ path = Logging; sourceTree = ""; }; - 076087FC60064C702EF94796 /* View */ = { - isa = PBXGroup; - children = ( - 5F8002D0392A476D2758B291 /* AnalyticsPromptScreen.swift */, - 745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */, - ); - path = View; - sourceTree = ""; - }; 0787F81684E503024BD0C051 /* Services */ = { isa = PBXGroup; children = ( @@ -2621,19 +2638,6 @@ path = View; sourceTree = ""; }; - 3153FCA3F4B0E88B16D99D12 /* SessionVerificationScreen */ = { - isa = PBXGroup; - children = ( - 2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */, - 4B5046BB295AEAFA6FB81655 /* SessionVerificationScreenModels.swift */, - 1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */, - ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */, - B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */, - 914D71CD209A34A8C142CB93 /* View */, - ); - path = SessionVerificationScreen; - sourceTree = ""; - }; 31CE4DA53232AA534057F912 /* Mocks */ = { isa = PBXGroup; children = ( @@ -2679,6 +2683,7 @@ 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */, C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */, 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */, + DE7C80EF77AD102053D3646E /* RoundedLabelItem.swift */, E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */, ); path = Views; @@ -2704,6 +2709,18 @@ path = View; sourceTree = ""; }; + 336A13CA8A1DD526D9C41DD4 /* IdentityConfirmationScreen */ = { + isa = PBXGroup; + children = ( + 7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */, + 7FDF541AE914059942B575B4 /* IdentityConfirmationScreenModels.swift */, + EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */, + 7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */, + DDC32FD8B94AA19C4FC062AD /* View */, + ); + path = IdentityConfirmationScreen; + sourceTree = ""; + }; 337015ADFBA3AB96660DB3A6 /* Generated */ = { isa = PBXGroup; children = ( @@ -2862,6 +2879,14 @@ path = SupportingFiles; sourceTree = ""; }; + 4044C040B64B9F077298C947 /* View */ = { + isa = PBXGroup; + children = ( + E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 405B00F139AEE3994601B36A = { isa = PBXGroup; children = ( @@ -3092,7 +3117,6 @@ 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */, ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */, C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */, - E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */, 037A5661B26EC6BE068188D7 /* Filters */, ); path = View; @@ -3166,6 +3190,7 @@ 0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */, A9B069D7772DDF6513E0F1B8 /* AuthenticationFlowCoordinator.swift */, 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */, + C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */, 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */, 0833F51229E166BCA141D004 /* RoomRolesAndPermissionsFlowCoordinator.swift */, D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */, @@ -3263,18 +3288,6 @@ path = SecureBackupLogoutConfirmationScreen; sourceTree = ""; }; - 669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */ = { - isa = PBXGroup; - children = ( - 9B65A314DF40B6BBF775C2BC /* AnalyticsPromptScreenCoordinator.swift */, - 840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */, - 63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */, - 8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */, - 076087FC60064C702EF94796 /* View */, - ); - path = AnalyticsPromptScreen; - sourceTree = ""; - }; 6709362D60732DED2069AE0F /* MediaPlayer */ = { isa = PBXGroup; children = ( @@ -3333,6 +3346,14 @@ path = TimelineItemContent; sourceTree = ""; }; + 6D7503E64A458DD09E65A3F7 /* View */ = { + isa = PBXGroup; + children = ( + 307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 6DE13A7AE6587B079F4049D7 /* Notification */ = { isa = PBXGroup; children = ( @@ -3701,6 +3722,11 @@ 7DA2A18CFD03E0BACE6B5C4B /* AnalyticsPromptScreen */ = { isa = PBXGroup; children = ( + 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */, + 18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */, + 6C8EC6EA7EDFCE46710DA306 /* AnalyticsPromptScreenViewModel.swift */, + 7AFD012C3A9F5EF276DDD4AA /* AnalyticsPromptScreenViewModelProtocol.swift */, + 4044C040B64B9F077298C947 /* View */, ); path = AnalyticsPromptScreen; sourceTree = ""; @@ -3866,6 +3892,10 @@ isa = PBXGroup; children = ( 7DA2A18CFD03E0BACE6B5C4B /* AnalyticsPromptScreen */, + 336A13CA8A1DD526D9C41DD4 /* IdentityConfirmationScreen */, + F6D661C666128C74BF0A7482 /* IdentityConfirmedScreen */, + F526F4FE387B32380592BA53 /* NotificationPermissionsScreen */, + C1CD278862878F9545608040 /* SessionVerificationScreen */, ); path = Onboarding; sourceTree = ""; @@ -3909,14 +3939,6 @@ path = LoginScreen; sourceTree = ""; }; - 914D71CD209A34A8C142CB93 /* View */ = { - isa = PBXGroup; - children = ( - 84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */, - ); - path = View; - sourceTree = ""; - }; 92E99C57D7F92ED16F73282C /* ElementCall */ = { isa = PBXGroup; children = ( @@ -4054,6 +4076,14 @@ path = Templates; sourceTree = ""; }; + 9C0C1CB9E16302C00AB4956D /* View */ = { + isa = PBXGroup; + children = ( + 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 9C4193C4524B35FD6B94B5A9 /* Pills */ = { isa = PBXGroup; children = ( @@ -4195,6 +4225,14 @@ path = LayoutTests; sourceTree = ""; }; + A722D372674EE5687E1A67E4 /* View */ = { + isa = PBXGroup; + children = ( + 5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */, + ); + path = View; + sourceTree = ""; + }; A78C2592419CA4C76FBA8FD2 /* Application */ = { isa = PBXGroup; children = ( @@ -4360,7 +4398,6 @@ CA8F098AE48D958B4257EB24 /* CallInviteRoomTimelineView.swift */, 6E2656184491C505700D2405 /* CollapsibleRoomTimelineView.swift */, 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */, - 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */, 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */, E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */, F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */, @@ -4515,6 +4552,19 @@ path = CreateRoom; sourceTree = ""; }; + C1CD278862878F9545608040 /* SessionVerificationScreen */ = { + isa = PBXGroup; + children = ( + 796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */, + AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */, + 161CD412E75F4086F422AE39 /* SessionVerificationScreenStateMachine.swift */, + ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */, + A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */, + A722D372674EE5687E1A67E4 /* View */, + ); + path = SessionVerificationScreen; + sourceTree = ""; + }; CA15BB3F6C62B35AE2C281A9 /* Provider */ = { isa = PBXGroup; children = ( @@ -4634,7 +4684,6 @@ D977D4E565C06D3F41C8F8FC /* Virtual */ = { isa = PBXGroup; children = ( - D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */, 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */, DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */, C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */, @@ -4663,6 +4712,14 @@ path = View; sourceTree = ""; }; + DDC32FD8B94AA19C4FC062AD /* View */ = { + isa = PBXGroup; + children = ( + 00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */, + ); + path = View; + sourceTree = ""; + }; DDF77194AB6E167891D0A8F3 /* View */ = { isa = PBXGroup; children = ( @@ -4716,7 +4773,6 @@ E59565F441830B19DBAE567C /* Screens */ = { isa = PBXGroup; children = ( - 669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */, 13263FFEA7749D822B51AA90 /* AppLock */, E74CD7681375AD2EAA34D66B /* Authentication */, 295BCC81AB45927F5F2033B1 /* AuthenticationStartScreen */, @@ -4751,7 +4807,6 @@ 7B890CCD20B037760BFDF957 /* RoomRolesAndPermissionsScreen */, 679E9837ECA8D6776079D16E /* RoomScreen */, 2565414373E6F68005966B8E /* SecureBackup */, - 3153FCA3F4B0E88B16D99D12 /* SessionVerificationScreen */, 70B74A432C241E56A7ACE610 /* Settings */, EC4545C7E37E8294D3FE6800 /* StartChatScreen */, ); @@ -4912,6 +4967,18 @@ path = View; sourceTree = ""; }; + F526F4FE387B32380592BA53 /* NotificationPermissionsScreen */ = { + isa = PBXGroup; + children = ( + 91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */, + 1DB2FC2AA9A07EE792DF65CF /* NotificationPermissionsScreenModels.swift */, + BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */, + 20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */, + 9C0C1CB9E16302C00AB4956D /* View */, + ); + path = NotificationPermissionsScreen; + sourceTree = ""; + }; F5A65D1D3B83593598DC278D /* EmojiPickerScreen */ = { isa = PBXGroup; children = ( @@ -4924,6 +4991,18 @@ path = EmojiPickerScreen; sourceTree = ""; }; + F6D661C666128C74BF0A7482 /* IdentityConfirmedScreen */ = { + isa = PBXGroup; + children = ( + 595EC503DA5517BBE6D39406 /* IdentityConfirmedScreenCoordinator.swift */, + 3DBE70FFB7936F35811772C1 /* IdentityConfirmedScreenModels.swift */, + 9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */, + 8319173DD66C07F45DC48848 /* IdentityConfirmedScreenViewModelProtocol.swift */, + 6D7503E64A458DD09E65A3F7 /* View */, + ); + path = IdentityConfirmedScreen; + sourceTree = ""; + }; F84100ED0C09031BAB7BB77E /* View */ = { isa = PBXGroup; children = ( @@ -5638,11 +5717,11 @@ F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */, 54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */, 8DDC6F28C797D8685F2F8E32 /* AnalyticsConsentState.swift in Sources */, - 9DF3F6318A4402305F5EB869 /* AnalyticsPromptScreen.swift in Sources */, - 5F28C9146694B381BB82E18C /* AnalyticsPromptScreenCoordinator.swift in Sources */, - 496CC9D59ACFAB84FD9B3B5F /* AnalyticsPromptScreenModels.swift in Sources */, - 0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */, - D4ACF3276F5D0DA28D4028C9 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */, + 24A1BBADAC43DC3F3A7347DA /* AnalyticsPromptScreen.swift in Sources */, + DCFE7CB3B9A104330BBB96AD /* AnalyticsPromptScreenCoordinator.swift in Sources */, + 6DC8E43BA04AC2AC4EB2EB97 /* AnalyticsPromptScreenModels.swift in Sources */, + DB65401349C143DFF883E2B0 /* AnalyticsPromptScreenViewModel.swift in Sources */, + 05BAB510CBC2ED35C154ADD0 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */, 3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */, 020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */, 95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */, @@ -5812,8 +5891,6 @@ 8B41D0357B91CD3B6F6A3BCA /* EmoteRoomTimelineItemContent.swift in Sources */, 68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */, 8B1D5CE017EEC734CF5FE130 /* Encodable.swift in Sources */, - B09DC6E3D0EE87C4D4ABFAB3 /* EncryptedHistoryRoomTimelineItem.swift in Sources */, - 9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */, 4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */, B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */, FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */, @@ -5850,9 +5927,18 @@ B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */, 0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */, A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */, - 584590D0EA548152A393E72C /* HomeScreenSessionVerificationBanner.swift in Sources */, DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */, 56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */, + 2BBE320EE426A347AAE5C7DA /* IdentityConfirmationScreen.swift in Sources */, + C3BB6887CF13B19182E81F87 /* IdentityConfirmationScreenCoordinator.swift in Sources */, + 77574A519A4E484880053EAD /* IdentityConfirmationScreenModels.swift in Sources */, + 0C346A4AD174F441EDB1414E /* IdentityConfirmationScreenViewModel.swift in Sources */, + 489BB6A733D3DA0FE7062650 /* IdentityConfirmationScreenViewModelProtocol.swift in Sources */, + 9C4EC28A921486B1775D7F8C /* IdentityConfirmedScreen.swift in Sources */, + 93AC1E8418D8C827671FB3A9 /* IdentityConfirmedScreenCoordinator.swift in Sources */, + D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */, + 01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */, + AADE7C2497A7B55D8BED7BD6 /* IdentityConfirmedScreenViewModelProtocol.swift in Sources */, BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */, 85813D87DDD7F67A46BD9AF7 /* ImageProviderProtocol.swift in Sources */, 7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */, @@ -5970,6 +6056,11 @@ 652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */, 06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */, C4C84901ABAC9B17564AB7EB /* NotificationName.swift in Sources */, + 5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */, + 454311EAC17D778E19F46592 /* NotificationPermissionsScreenCoordinator.swift in Sources */, + 3E7B65C2C97748D5D65AAA8B /* NotificationPermissionsScreenModels.swift in Sources */, + D4D7CCECC6C0AAFC42E165BB /* NotificationPermissionsScreenViewModel.swift in Sources */, + C8A9C595038AFA2D707AC8C1 /* NotificationPermissionsScreenViewModelProtocol.swift in Sources */, C0090506A52A1991BAF4BA68 /* NotificationSettingsChatType.swift in Sources */, AA93B3F9B5DD097DEF79F981 /* NotificationSettingsEditScreen.swift in Sources */, 53A59720F4729D9BBFFB7CAB /* NotificationSettingsEditScreenCoordinator.swift in Sources */, @@ -5987,6 +6078,7 @@ CBD2ABE4C1A47ECD99E1488E /* NotificationSettingsScreenViewModelProtocol.swift in Sources */, 523C6800ED85D5810CF18C19 /* OIDCAccountSettingsPresenter.swift in Sources */, 9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */, + 11A6B8E3CBDBF0A4107FF4CE /* OnboardingFlowCoordinator.swift in Sources */, 3CE4C5071B6D2576E2473989 /* OrderedSet.swift in Sources */, AA5924D3B67F7ACD98BBEFDC /* OrientationManagerProtocol.swift in Sources */, 804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */, @@ -6139,7 +6231,7 @@ 9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */, 77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */, B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */, - D8385A51A3D0FA9283556281 /* RoundedLabelItem.swift in Sources */, + BD11E639CF566A9DA8FCA717 /* RoundedLabelItem.swift in Sources */, 50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */, D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */, F4971845B5C4F270F6BC5745 /* ScaledFrameModifier.swift in Sources */, @@ -6187,12 +6279,12 @@ 237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */, AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */, 94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */, - 7A0A0929556792FB19B812C5 /* SessionVerificationScreen.swift in Sources */, - E9F148072F9513EC2272AA21 /* SessionVerificationScreenCoordinator.swift in Sources */, - 5770C4906668C6D3008A2AC9 /* SessionVerificationScreenModels.swift in Sources */, - B27D3190784F85916DA1C394 /* SessionVerificationScreenStateMachine.swift in Sources */, - F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */, - E570117376826665640F0CFD /* SessionVerificationScreenViewModelProtocol.swift in Sources */, + 707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */, + D02DEB36D32A72A1B365E452 /* SessionVerificationScreenCoordinator.swift in Sources */, + 5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */, + 601AB75BD52B0B4276CEB84A /* SessionVerificationScreenStateMachine.swift in Sources */, + 4A8287E5281B44A8754BE509 /* SessionVerificationScreenViewModel.swift in Sources */, + 762DB0973865293F0C3D3D7B /* SessionVerificationScreenViewModelProtocol.swift in Sources */, 755395927DDD6EBDDA5E217A /* SettingsFlowCoordinator.swift in Sources */, 34F1261CEF6D6A00D559B520 /* SettingsScreen.swift in Sources */, AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */, @@ -6737,7 +6829,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 16.4; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER)"; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 1.5.13; + MARKETING_VERSION = 1.6.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6813,7 +6905,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 16.4; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER)"; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 1.5.13; + MARKETING_VERSION = 1.6.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; diff --git a/ElementX/Resources/Assets.xcassets/images/background-bottom.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/images/background-bottom.imageset/Contents.json new file mode 100644 index 000000000..3ca614ee7 --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/images/background-bottom.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "background-bottom-light.png", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "background-bottom-dark.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElementX/Resources/Assets.xcassets/images/background-bottom.imageset/background-bottom-dark.png b/ElementX/Resources/Assets.xcassets/images/background-bottom.imageset/background-bottom-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..075078e12d5647fede7a0105784fdfb22b723d73 GIT binary patch literal 96298 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 WQaGs2O`UrH0000yDt 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

W7|QK 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ElementX/Resources/Assets.xcassets/images/notifications-prompt-graphic.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/images/notifications-prompt-graphic.imageset/Contents.json new file mode 100644 index 000000000..643d2effb --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/images/notifications-prompt-graphic.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Alerts.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 55794b14f..b19aa1c28 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -398,6 +398,10 @@ "screen_edit_profile_error_title" = "Unable to update profile"; "screen_edit_profile_title" = "Edit profile"; "screen_edit_profile_updating_details" = "Updating profile…"; +"screen_identity_confirmation_subtitle" = "Verify this device to set up secure messaging."; +"screen_identity_confirmation_title" = "Confirm that it's you"; +"screen_identity_confirmed_subtitle" = "Now you can read or send messages securely, and anyone you chat with can also trust this device."; +"screen_identity_confirmed_title" = "Device verified"; "screen_invites_decline_chat_message" = "Are you sure you want to decline the invitation to join %1$@?"; "screen_invites_decline_chat_title" = "Decline invite"; "screen_invites_decline_direct_chat_message" = "Are you sure you want to decline this private chat with %1$@?"; diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 0eec50461..095a5802d 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -83,7 +83,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg MXLog.info("\(appName) \(appVersion) (\(appBuild))") if ProcessInfo.processInfo.environment["RESET_APP_SETTINGS"].map(Bool.init) == true { - AppSettings.reset() + AppSettings.resetAllSettings() } self.appDelegate = appDelegate @@ -154,11 +154,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg return } - if appSettings.appLockIsMandatory, !appLockFlowCoordinator.appLockService.isEnabled { - stateMachine.processEvent(.startWithAppLockSetup) - } else { - stateMachine.processEvent(.startWithExistingSession) - } + stateMachine.processEvent(.startWithExistingSession) } func stop() { @@ -319,13 +315,21 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg appSettings.migratedAccounts[userID] = true } } + + if oldVersion < Version(1, 6, 0) { + MXLog.info("Migrating to v1.6.0, marking identity confirmation onboarding as ran.") + if !userSessionStore.userIDs.isEmpty { + appSettings.hasRunIdentityConfirmationOnboarding = true + appSettings.hasRunNotificationPermissionsOnboarding = true + } + } } /// Clears the keychain, app support directory etc ready for a fresh use. /// - Parameter includingSettings: Whether to additionally wipe the user's app settings too. private func wipeUserData(includingSettings: Bool = false) { if includingSettings { - AppSettings.reset() + AppSettings.resetAllSettings() appLockFlowCoordinator.appLockService.disable() } userSessionStore.reset() @@ -339,20 +343,15 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg case (.initial, .startWithAuthentication, .signedOut): startAuthentication() case (.signedOut, .createdUserSession, .signedIn): - setupUserSession() + setupUserSession(isNewLogin: true) case (.initial, .startWithExistingSession, .restoringSession): restoreUserSession() case (.restoringSession, .failedRestoringSession, .signedOut): showLoginErrorToast() presentSplashScreen() case (.restoringSession, .createdUserSession, .signedIn): - setupUserSession() - - case (.initial, .startWithAppLockSetup, .mandatoryAppLockSetup): - startMandatoryAppLockSetup() - case (.mandatoryAppLockSetup, .appLockSetupComplete, .restoringSession): - restoreUserSession() - + setupUserSession(isNewLogin: false) + case (.signingOut, .signOut, .signingOut): // We can ignore signOut when already in the process of signing out, // such as the SDK sending an authError due to token invalidation. @@ -397,7 +396,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg encryptionKeyProvider: EncryptionKeyProvider(), appSettings: appSettings) authenticationFlowCoordinator = AuthenticationFlowCoordinator(authenticationService: authenticationService, - appLockService: appLockFlowCoordinator.appLockService, bugReportService: ServiceLocator.shared.bugReportService, navigationRootCoordinator: navigationRootCoordinator, appSettings: appSettings, @@ -450,7 +448,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg } } - private func setupUserSession() { + private func setupUserSession(isNewLogin: Bool) { guard let userSession else { fatalError("User session not setup") } @@ -462,9 +460,11 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg bugReportService: ServiceLocator.shared.bugReportService, roomTimelineControllerFactory: RoomTimelineControllerFactory(), appSettings: appSettings, - analytics: ServiceLocator.shared.analytics) + analytics: ServiceLocator.shared.analytics, + notificationManager: notificationManager, + isNewLogin: isNewLogin) - userSessionFlowCoordinator.actions + userSessionFlowCoordinator.actionsPublisher .sink { [weak self] action in guard let self else { return } @@ -487,32 +487,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg userSessionFlowCoordinator.handleAppRoute(storedAppRoute, animated: false) } } - - /// Used to add a PIN code to an existing session that somehow missed out mandatory PIN setup. - private func startMandatoryAppLockSetup() { - MXLog.info("Mandatory App Lock enabled but no PIN is set. Showing the setup flow.") - let navigationCoordinator = NavigationStackCoordinator() - let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding, - appLockService: appLockFlowCoordinator.appLockService, - navigationStackCoordinator: navigationCoordinator) - coordinator.actions.sink { [weak self] action in - guard let self else { return } - switch action { - case .complete: - stateMachine.processEvent(.appLockSetupComplete) - appLockSetupFlowCoordinator = nil - case .forceLogout: - fatalError("Creating a PIN shouldn't be able to fail in this way") - } - } - .store(in: &cancellables) - - appLockSetupFlowCoordinator = coordinator - navigationRootCoordinator.setRootCoordinator(navigationCoordinator) - coordinator.start() - } - private func logout(isSoft: Bool) { guard let userSession else { fatalError("User session not setup") @@ -544,6 +519,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg userSessionStore.logout(userSession: userSession) tearDownUserSession() + AppSettings.resetSessionSpecificSettings() + // Reset analytics ServiceLocator.shared.analytics.optOut() ServiceLocator.shared.analytics.resetConsentState() @@ -591,7 +568,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg private func configureNotificationManager() { notificationManager.setUserSession(userSession) - notificationManager.requestAuthorization() appDelegateObserver = appDelegate.callbacks .receive(on: DispatchQueue.main) @@ -689,7 +665,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg // MARK: Toasts and loading indicators - private static let loadingIndicatorIdentifier = "AppCoordinatorLoading" + private static let loadingIndicatorIdentifier = "\(AppCoordinator.self)-Loading" private func showLoadingIndicator() { ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, diff --git a/ElementX/Sources/Application/AppCoordinatorStateMachine.swift b/ElementX/Sources/Application/AppCoordinatorStateMachine.swift index 9de14967c..f4806073d 100644 --- a/ElementX/Sources/Application/AppCoordinatorStateMachine.swift +++ b/ElementX/Sources/Application/AppCoordinatorStateMachine.swift @@ -28,12 +28,7 @@ class AppCoordinatorStateMachine { case softLogout /// Opening an existing session. case restoringSession - - /// Showing the mandatory app lock setup flow before restoring the session. - /// This state should only be allowed before restoring an existing session. For - /// new users the setup is inserted in the middle of the authentication flow. - case mandatoryAppLockSetup - + /// User session started case signedIn @@ -48,21 +43,13 @@ class AppCoordinatorStateMachine { /// Start the `AppCoordinator` by restoring an existing account. case startWithExistingSession - - /// Start the `AppCoordinator` by showing the mandatory PIN creation flow. - /// This event should only be sent if an account exists and a mandatory PIN is - /// missing. Normally it will be handled as part of the authentication flow. - case startWithAppLockSetup /// Restoring session failed. case failedRestoringSession /// A session has been created. case createdUserSession - - /// The app lock setup has been completed. - case appLockSetupComplete - + /// Request sign out. case signOut(isSoft: Bool, disableAppLock: Bool) /// Request the soft logout screen. @@ -92,10 +79,7 @@ class AppCoordinatorStateMachine { stateMachine.addRoutes(event: .startWithExistingSession, transitions: [.initial => .restoringSession]) stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn]) stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut]) - - stateMachine.addRoutes(event: .startWithAppLockSetup, transitions: [.initial => .mandatoryAppLockSetup]) - stateMachine.addRoutes(event: .appLockSetupComplete, transitions: [.mandatoryAppLockSetup => .restoringSession]) - + stateMachine.addRoutes(event: .completedSigningOut, transitions: [.signingOut(isSoft: false, disableAppLock: false) => .signedOut, .signingOut(isSoft: false, disableAppLock: true) => .signedOut]) stateMachine.addRoutes(event: .showSoftLogout, transitions: [.signingOut(isSoft: true, disableAppLock: false) => .softLogout]) diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 6b5115097..cb63f2fba 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -24,10 +24,13 @@ final class AppSettings { case seenInvites case appLockNumberOfPINAttempts case appLockNumberOfBiometricAttempts - case lastLoginDate case migratedAccounts case timelineStyle + case analyticsConsentState + case hasRunNotificationPermissionsOnboarding + case hasRunIdentityConfirmationOnboarding + case enableNotifications case enableInAppNotifications case pusherProfileTag @@ -59,11 +62,16 @@ final class AppSettings { #if IS_MAIN_APP - static func reset() { + static func resetAllSettings() { MXLog.warning("Resetting the AppSettings.") store.removePersistentDomain(forName: suiteName) } + static func resetSessionSpecificSettings() { + MXLog.warning("Resetting the user session specific AppSettings.") + store.removeObject(forKey: UserDefaultsKeys.hasRunIdentityConfirmationOnboarding.rawValue) + } + static func configureWithSuiteName(_ name: String) { suiteName = name @@ -120,7 +128,9 @@ final class AppSettings { let privacyURL: URL = "https://element.io/privacy" /// An email address that should be used for support requests. let supportEmailAddress = "support@element.io" - // A URL where users can go read more about the chat backup. + /// A URL where users can go read more about encryption in general. + let encryptionURL: URL = "https://element.io/help#encryption" + /// A URL where users can go read more about the chat backup. let chatBackupDetailsURL: URL = "https://element.io/help#encryption5" @UserPreference(key: UserDefaultsKeys.appAppearance, defaultValue: .system, storageType: .userDefaults(store)) @@ -153,13 +163,6 @@ final class AppSettings { return url }() - - /// The date that the call to `/login` completed successfully. This is used to put - /// a hard wall on the history of encrypted messages until we have key backup. - /// - /// Not a multi-account aware setting as key backup will come before multi-account. - @UserPreference(key: UserDefaultsKeys.lastLoginDate, defaultValue: nil, storageType: .userDefaults(store)) - var lastLoginDate: Date? /// A dictionary of accounts that have performed an initial sync through their proxy. /// @@ -211,6 +214,12 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store)) var analyticsConsentState + @UserPreference(key: UserDefaultsKeys.hasRunNotificationPermissionsOnboarding, defaultValue: false, storageType: .userDefaults(store)) + var hasRunNotificationPermissionsOnboarding + + @UserPreference(key: UserDefaultsKeys.hasRunIdentityConfirmationOnboarding, defaultValue: false, storageType: .userDefaults(store)) + var hasRunIdentityConfirmationOnboarding + // MARK: - Home Screen @UserPreference(key: UserDefaultsKeys.hideUnreadMessagesBadge, defaultValue: false, storageType: .userDefaults(store)) diff --git a/ElementX/Sources/FlowCoordinators/AuthenticationFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/AuthenticationFlowCoordinator.swift index 2d051e547..dd38ba5b7 100644 --- a/ElementX/Sources/FlowCoordinators/AuthenticationFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/AuthenticationFlowCoordinator.swift @@ -24,7 +24,6 @@ protocol AuthenticationFlowCoordinatorDelegate: AnyObject { class AuthenticationFlowCoordinator: FlowCoordinatorProtocol { private let authenticationService: AuthenticationServiceProxyProtocol - private let appLockService: AppLockServiceProtocol private let bugReportService: BugReportServiceProtocol private let navigationRootCoordinator: NavigationRootCoordinator private let navigationStackCoordinator: NavigationStackCoordinator @@ -36,23 +35,18 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol { private var oidcPresenter: OIDCAuthenticationPresenter? - // periphery: ignore - used to store the coordinator to avoid deallocation - private var appLockFlowCoordinator: AppLockSetupFlowCoordinator? - // periphery:ignore - retaining purpose private var bugReportFlowCoordinator: BugReportFlowCoordinator? weak var delegate: AuthenticationFlowCoordinatorDelegate? init(authenticationService: AuthenticationServiceProxyProtocol, - appLockService: AppLockServiceProtocol, bugReportService: BugReportServiceProtocol, navigationRootCoordinator: NavigationRootCoordinator, appSettings: AppSettings, analytics: AnalyticsService, userIndicatorController: UserIndicatorControllerProtocol) { self.authenticationService = authenticationService - self.appLockService = appLockService self.bugReportService = bugReportService self.navigationRootCoordinator = navigationRootCoordinator self.appSettings = appSettings @@ -108,7 +102,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol { private func showReportProblemScreen() { bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(navigationStackCoordinator), - userIndicatorController: ServiceLocator.shared.userIndicatorController, + userIndicatorController: userIndicatorController, bugReportService: bugReportService, userID: nil, deviceID: nil)) @@ -265,58 +259,10 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol { } private func userHasSignedIn(userSession: UserSessionProtocol) { - appSettings.lastLoginDate = .now - - if appSettings.appLockIsMandatory, !appLockService.isEnabled { - showAppLockSetupFlow(userSession: userSession) - } else if analytics.shouldShowAnalyticsPrompt { - showAnalyticsPrompt(userSession: userSession) - } else { - delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession) - } + delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession) } - private func showAppLockSetupFlow(userSession: UserSessionProtocol) { - let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding, - appLockService: appLockService, - navigationStackCoordinator: navigationStackCoordinator) - coordinator.actions.sink { [weak self] action in - guard let self else { return } - switch action { - case .complete: - appLockFlowCoordinator = nil - if analytics.shouldShowAnalyticsPrompt { - showAnalyticsPrompt(userSession: userSession) - } else { - delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession) - } - case .forceLogout: - fatalError("The PIN creation flow should not fail.") - } - } - .store(in: &cancellables) - - appLockFlowCoordinator = coordinator - coordinator.start() - } - - private func showAnalyticsPrompt(userSession: UserSessionProtocol) { - let coordinator = AnalyticsPromptScreenCoordinator(analytics: analytics, termsURL: appSettings.analyticsConfiguration.termsURL) - - coordinator.actions - .sink { [weak self] action in - guard let self else { return } - switch action { - case .done: - delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession) - } - } - .store(in: &cancellables) - - navigationStackCoordinator.push(coordinator) - } - - private static let loadingIndicatorIdentifier = "authenticationFlowCoordinatorLoading" + private static let loadingIndicatorIdentifier = "\(AuthenticationFlowCoordinator.self)-Loading" private func startLoading() { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, diff --git a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift new file mode 100644 index 000000000..51b38350b --- /dev/null +++ b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift @@ -0,0 +1,343 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation +import SwiftState + +class OnboardingFlowCoordinator: FlowCoordinatorProtocol { + private let userSession: UserSessionProtocol + private let appLockService: AppLockServiceProtocol + private let analyticsService: AnalyticsService + private let appSettings: AppSettings + private let notificationManager: NotificationManagerProtocol + private let rootNavigationStackCoordinator: NavigationStackCoordinator + private let userIndicatorController: UserIndicatorControllerProtocol + private let isNewLogin: Bool + + private var navigationStackCoordinator: NavigationStackCoordinator! + + enum State: StateType { + case initial + case identityConfirmation + case identityConfirmed + case appLockSetup + case analyticsPrompt + case notificationPermissions + case finished + } + + enum Event: EventType { + case next + } + + private let stateMachine: StateMachine + private var cancellables = Set() + + // periphery: ignore - used to store the coordinator to avoid deallocation + private var appLockFlowCoordinator: AppLockSetupFlowCoordinator? + + init(userSession: UserSessionProtocol, + appLockService: AppLockServiceProtocol, + analyticsService: AnalyticsService, + appSettings: AppSettings, + notificationManager: NotificationManagerProtocol, + navigationStackCoordinator: NavigationStackCoordinator, + userIndicatorController: UserIndicatorControllerProtocol, + isNewLogin: Bool) { + self.userSession = userSession + self.appLockService = appLockService + self.analyticsService = analyticsService + self.appSettings = appSettings + self.notificationManager = notificationManager + self.userIndicatorController = userIndicatorController + self.isNewLogin = isNewLogin + + rootNavigationStackCoordinator = navigationStackCoordinator + self.navigationStackCoordinator = NavigationStackCoordinator() + + stateMachine = .init(state: .initial) + } + + var shouldStart: Bool { + guard stateMachine.state == .initial, !ProcessInfo.isRunningIntegrationTests else { + return false + } + + return isNewLogin || requiresVerification || requiresAppLockSetup || requiresAnalyticsSetup || requiresNotificationsSetup + } + + func start() { + guard shouldStart else { + fatalError("This flow coordinator shouldn't have been started") + } + + configureStateMachine() + + stateMachine.tryEvent(.next) + + rootNavigationStackCoordinator.setFullScreenCoverCoordinator(navigationStackCoordinator, animated: !isNewLogin) + } + + func handleAppRoute(_ appRoute: AppRoute, animated: Bool) { + fatalError() + } + + func clearRoute(animated: Bool) { + fatalError() + } + + // MARK: - Private + + private var requiresVerification: Bool { + // We want to make sure onboarding finishes but also every time the user becomes unverified (e.g. account reset) + !appSettings.hasRunIdentityConfirmationOnboarding || userSession.sessionSecurityStatePublisher.value.verificationState == .unverified + } + + private var requiresAppLockSetup: Bool { + appSettings.appLockIsMandatory && !appLockService.isEnabled + } + + private var requiresAnalyticsSetup: Bool { + analyticsService.shouldShowAnalyticsPrompt + } + + private var requiresNotificationsSetup: Bool { + !appSettings.hasRunNotificationPermissionsOnboarding + } + + private func configureStateMachine() { + stateMachine.addRouteMapping { [weak self] _, fromState, _ in + guard let self else { + return nil + } + + switch (fromState, requiresVerification, requiresAppLockSetup, requiresAnalyticsSetup, requiresNotificationsSetup) { + case (.initial, true, _, _, _): + return .identityConfirmation + case (.initial, false, true, _, _): + return .appLockSetup + case (.initial, false, false, true, _): + return .analyticsPrompt + case (.initial, false, false, false, true): + return .notificationPermissions + + case (.identityConfirmation, _, _, _, _): + return .identityConfirmed + + case (.identityConfirmed, _, true, _, _): + return .appLockSetup + case (.identityConfirmed, _, false, true, _): + return .analyticsPrompt + case (.identityConfirmed, _, false, false, true): + return .notificationPermissions + case (.identityConfirmed, _, false, false, false): + return .finished + + case (.appLockSetup, _, _, true, _): + return .analyticsPrompt + case (.appLockSetup, _, _, false, true): + return .notificationPermissions + case (.appLockSetup, _, _, false, false): + return .finished + + case (.analyticsPrompt, _, _, _, true): + return .notificationPermissions + case (.analyticsPrompt, _, _, _, false): + return .finished + + case (.notificationPermissions, _, _, _, _): + return .finished + + default: + return nil + } + } + + stateMachine.addAnyHandler(.any => .any) { [weak self] context in + guard let self else { return } + + switch (context.fromState, context.event, context.toState) { + case (_, _, .identityConfirmation): + presentIdentityConfirmationScreen() + case (_, _, .identityConfirmed): + presentIdentityConfirmedScreen() + case (_, _, .appLockSetup): + presentAppLockSetupFlow() + case (_, _, .analyticsPrompt): + presentAnalyticsPromptScreen() + case (_, _, .notificationPermissions): + presentNotificationPermissionsScreen() + case (_, _, .finished): + rootNavigationStackCoordinator.setFullScreenCoverCoordinator(nil) + default: + fatalError("Unknown transition: \(context)") + } + } + + stateMachine.addErrorHandler { context in + fatalError("Unexpected transition: \(context)") + } + } + + private func presentIdentityConfirmationScreen() { + let parameters = IdentityConfirmationScreenCoordinatorParameters(userSession: userSession, + appSettings: appSettings, + userIndicatorController: userIndicatorController) + + let coordinator = IdentityConfirmationScreenCoordinator(parameters: parameters) + coordinator.actionsPublisher.sink { [weak self] action in + guard let self else { return } + + switch action { + case .otherDevice: + Task { + await self.presentSessionVerificationScreen() + } + case .recoveryKey: + presentRecoveryKeyScreen() + } + } + .store(in: &cancellables) + + presentCoordinator(coordinator) + } + + private func presentSessionVerificationScreen() async { + guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else { + fatalError("The sessionVerificationController should aways be valid at this point") + } + + let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController) + + let coordinator = SessionVerificationScreenCoordinator(parameters: parameters) + + coordinator.actions + .sink { [weak self] action in + guard let self else { return } + + switch action { + case .done: + appSettings.hasRunIdentityConfirmationOnboarding = true + stateMachine.tryEvent(.next) + } + } + .store(in: &cancellables) + + presentCoordinator(coordinator) + } + + private func presentRecoveryKeyScreen() { + let parameters = SecureBackupRecoveryKeyScreenCoordinatorParameters(secureBackupController: userSession.clientProxy.secureBackupController, + userIndicatorController: ServiceLocator.shared.userIndicatorController, + isModallyPresented: false) + + let coordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: parameters) + + coordinator.actions + .sink { [weak self] action in + guard let self else { return } + + switch action { + case .recoveryFixed: + appSettings.hasRunIdentityConfirmationOnboarding = true + stateMachine.tryEvent(.next) + default: + fatalError("Other flows shouldn't be possible") + } + } + .store(in: &cancellables) + + presentCoordinator(coordinator) + } + + private func presentIdentityConfirmedScreen() { + let coordinator = IdentityConfirmedScreenCoordinator(parameters: .init()) + coordinator.actionsPublisher + .sink { [weak self] action in + guard let self else { return } + + switch action { + case .done: + stateMachine.tryEvent(.next) + } + } + .store(in: &cancellables) + + presentCoordinator(coordinator) + } + + private func presentAppLockSetupFlow() { + let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding, + appLockService: appLockService, + navigationStackCoordinator: navigationStackCoordinator) + coordinator.actions.sink { [weak self] action in + guard let self else { return } + switch action { + case .complete: + appLockFlowCoordinator = nil + stateMachine.tryEvent(.next) + case .forceLogout: + fatalError("The PIN creation flow should not fail.") + } + } + .store(in: &cancellables) + + appLockFlowCoordinator = coordinator + coordinator.start() + } + + private func presentAnalyticsPromptScreen() { + let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: appSettings.analyticsConfiguration.termsURL) + + coordinator.actions + .sink { [weak self] action in + guard let self else { return } + switch action { + case .done: + stateMachine.tryEvent(.next) + } + } + .store(in: &cancellables) + + presentCoordinator(coordinator) + } + + private func presentNotificationPermissionsScreen() { + let coordinator = NotificationPermissionsScreenCoordinator(parameters: .init(notificationManager: notificationManager)) + + coordinator.actions + .sink { [weak self] action in + guard let self else { return } + switch action { + case .done: + appSettings.hasRunNotificationPermissionsOnboarding = true + stateMachine.tryEvent(.next) + } + } + .store(in: &cancellables) + + presentCoordinator(coordinator) + } + + private func presentCoordinator(_ coordinator: CoordinatorProtocol) { + if navigationStackCoordinator.rootCoordinator == nil { + navigationStackCoordinator.setRootCoordinator(coordinator) + } else { + navigationStackCoordinator.push(coordinator) + } + } +} diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index f0eeb8ce9..4e84847e7 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -434,8 +434,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy, - timelineItemFactory: timelineItemFactory, - secureBackupController: userSession.clientProxy.secureBackupController) + timelineItemFactory: timelineItemFactory) self.timelineController = timelineController analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace) @@ -918,8 +917,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) let roomTimelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy, - timelineItemFactory: timelineItemFactory, - secureBackupController: userSession.clientProxy.secureBackupController) + timelineItemFactory: timelineItemFactory) let parameters = RoomPollsHistoryScreenCoordinatorParameters(roomProxy: roomProxy, pollInteractionHandler: PollInteractionHandler(analyticsService: analytics, roomProxy: roomProxy), diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 1ea9db505..c6e97f42f 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -130,10 +130,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { presentLegalInformationScreen() case .blockedUsers: presentBlockedUsersScreen() - case .sessionVerification: - Task { - await self.presentSessionVerificationScreen() - } case .accountSessions: presentAccountSessionsListURL() case .notifications: @@ -212,35 +208,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { userIndicatorController: parameters.userIndicatorController)) navigationStackCoordinator.push(coordinator) } - - private func presentSessionVerificationScreen() async { - guard case let .success(sessionVerificationController) = await parameters.userSession.clientProxy.sessionVerificationControllerProxy() else { - fatalError("The sessionVerificationController should aways be valid at this point") - } - let verificationParameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController, - recoveryState: parameters.userSession.sessionSecurityStatePublisher.value.recoveryState) - let coordinator = SessionVerificationScreenCoordinator(parameters: verificationParameters) - - coordinator.actions - .sink { [weak self] action in - guard let self else { return } - - switch action { - case .recoveryKey: - navigationStackCoordinator.setSheetCoordinator(nil) - handleAppRoute(.chatBackupSettings, animated: true) - case .done: - navigationStackCoordinator.setSheetCoordinator(nil) - } - } - .store(in: &cancellables) - - navigationStackCoordinator.setSheetCoordinator(coordinator) { [weak self] in - self?.navigationStackCoordinator.setSheetCoordinator(nil) - } - } - private func presentNotificationSettings() { let notificationParameters = NotificationSettingsScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator, userSession: parameters.userSession, diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index a13c99c9f..f121e9796 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -31,7 +31,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { private let windowManager: WindowManagerProtocol private let bugReportService: BugReportServiceProtocol private let appSettings: AppSettings - private let actionsSubject: PassthroughSubject = .init() private let stateMachine: UserSessionFlowCoordinatorStateMachine @@ -39,6 +38,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { private let settingsFlowCoordinator: SettingsFlowCoordinator + private let onboardingFlowCoordinator: OnboardingFlowCoordinator + // periphery:ignore - retaining purpose private var bugReportFlowCoordinator: BugReportFlowCoordinator? @@ -52,7 +53,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { private let selectedRoomSubject = CurrentValueSubject(nil) - var actions: AnyPublisher { + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { actionsSubject.eraseToAnyPublisher() } @@ -63,7 +65,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { bugReportService: BugReportServiceProtocol, roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol, appSettings: AppSettings, - analytics: AnalyticsService) { + analytics: AnalyticsService, + notificationManager: NotificationManagerProtocol, + isNewLogin: Bool) { stateMachine = UserSessionFlowCoordinatorStateMachine() self.userSession = userSession self.navigationRootCoordinator = navigationRootCoordinator @@ -98,8 +102,29 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { navigationSplitCoordinator: navigationSplitCoordinator, userIndicatorController: ServiceLocator.shared.userIndicatorController)) + onboardingFlowCoordinator = OnboardingFlowCoordinator(userSession: userSession, + appLockService: appLockService, + analyticsService: analytics, + appSettings: appSettings, + notificationManager: notificationManager, + navigationStackCoordinator: detailNavigationStackCoordinator, + userIndicatorController: ServiceLocator.shared.userIndicatorController, + isNewLogin: isNewLogin) + setupStateMachine() + userSession.sessionSecurityStatePublisher + .map(\.verificationState) + .filter { $0 != .unknown } + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self else { return } + + attemptStartingOnboarding() + } + .store(in: &cancellables) + roomFlowCoordinator.actions.sink { [weak self] action in guard let self else { return } @@ -208,11 +233,18 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { } func clearRoute(animated: Bool) { - fatalError("not necessary as of right now") + roomFlowCoordinator.clearRoute(animated: animated) } // MARK: - Private + func attemptStartingOnboarding() { + if onboardingFlowCoordinator.shouldStart { + clearRoute(animated: false) + onboardingFlowCoordinator.start() + } + } + private func clearPresentedSheets(animated: Bool, completion: @escaping () -> Void) { if navigationSplitCoordinator.sheetCoordinator == nil { completion() @@ -234,6 +266,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { switch (context.fromState, context.event, context.toState) { case (.initial, .start, .roomList): presentHomeScreen() + attemptStartingOnboarding() case(.roomList, .selectRoom, .roomList): break @@ -244,13 +277,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { break case (.invitesScreen, .deselectRoom, .invitesScreen): break - - case (.roomList, .showSessionVerificationScreen, .sessionVerificationScreen): - Task { - await self.presentSessionVerification(animated: animated) - } - case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .roomList): - break case (.roomList, .showSettingsScreen, .settingsScreen): break @@ -334,8 +360,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { settingsFlowCoordinator.handleAppRoute(.settings, animated: true) case .presentFeedbackScreen: stateMachine.processEvent(.feedbackScreen) - case .presentSessionVerificationScreen: - stateMachine.processEvent(.showSessionVerificationScreen) case .presentSecureBackupSettings: settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true) case .presentStartChatScreen: @@ -400,36 +424,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { presentSecureBackupLogoutConfirmationScreen() } - // MARK: Session verification - - private func presentSessionVerification(animated: Bool) async { - guard case let .success(sessionVerificationController) = await userSession.clientProxy.sessionVerificationControllerProxy() else { - fatalError("The sessionVerificationController should aways be valid at this point") - } - - let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController, - recoveryState: userSession.sessionSecurityStatePublisher.value.recoveryState) - - let coordinator = SessionVerificationScreenCoordinator(parameters: parameters) - - coordinator.actions - .sink { [weak self] action in - guard let self else { return } - - switch action { - case .recoveryKey: - settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true) - case .done: - navigationSplitCoordinator.setSheetCoordinator(nil) - } - } - .store(in: &cancellables) - - navigationSplitCoordinator.setSheetCoordinator(coordinator, animated: animated) { [weak self] in - self?.stateMachine.processEvent(.dismissedSessionVerificationScreen) - } - } - // MARK: Start Chat private func presentStartChat(animated: Bool) { diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift index 16727ac8c..e0154cbed 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift @@ -26,9 +26,6 @@ class UserSessionFlowCoordinatorStateMachine { /// Showing the home screen. The `selectedRoomID` represents the timeline shown on the detail panel (if any) case roomList(selectedRoomID: String?) - /// Showing the session verification flows - case sessionVerificationScreen(selectedRoomID: String?) - /// Showing the session verification flows case feedbackScreen(selectedRoomID: String?) @@ -70,11 +67,6 @@ class UserSessionFlowCoordinatorStateMachine { /// The feedback screen has been dismissed case dismissedFeedbackScreen - /// Request the start of the session verification flow - case showSessionVerificationScreen - /// Session verification has finished - case dismissedSessionVerificationScreen - /// Request the start of the start chat flow case showStartChatScreen /// Start chat has been dismissed @@ -126,11 +118,6 @@ class UserSessionFlowCoordinatorStateMachine { case (.feedbackScreen(let selectedRoomID), .dismissedFeedbackScreen): return .roomList(selectedRoomID: selectedRoomID) - case (.roomList(let selectedRoomID), .showSessionVerificationScreen): - return .sessionVerificationScreen(selectedRoomID: selectedRoomID) - case (.sessionVerificationScreen(let selectedRoomID), .dismissedSessionVerificationScreen): - return .roomList(selectedRoomID: selectedRoomID) - case (.roomList(let selectedRoomID), .showStartChatScreen): return .startChatScreen(selectedRoomID: selectedRoomID) case (.startChatScreen(let selectedRoomID), .dismissedStartChatScreen): diff --git a/ElementX/Sources/Generated/Assets.swift b/ElementX/Sources/Generated/Assets.swift index aebca381e..0d3120d35 100644 --- a/ElementX/Sources/Generated/Assets.swift +++ b/ElementX/Sources/Generated/Assets.swift @@ -33,6 +33,7 @@ internal enum Asset { internal enum Images { internal static let appLogo = ImageAsset(name: "images/app-logo") internal static let serverSelectionIcon = ImageAsset(name: "images/server-selection-icon") + internal static let backgroundBottom = ImageAsset(name: "images/background-bottom") internal static let closeRte = ImageAsset(name: "images/close-rte") internal static let composerAttachment = ImageAsset(name: "images/composer-attachment") internal static let stopRecording = ImageAsset(name: "images/stop-recording") @@ -41,6 +42,7 @@ internal enum Asset { internal static let locationMarkerShape = ImageAsset(name: "images/location-marker-shape") internal static let mediaPause = ImageAsset(name: "images/media-pause") internal static let mediaPlay = ImageAsset(name: "images/media-play") + internal static let notificationsPromptGraphic = ImageAsset(name: "images/notifications-prompt-graphic") internal static let waitingGradient = ImageAsset(name: "images/waiting-gradient") } } diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index c84393abd..0ba214ac1 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -991,6 +991,14 @@ internal enum L10n { internal static var screenEditProfileTitle: String { return L10n.tr("Localizable", "screen_edit_profile_title") } /// Updating profile… internal static var screenEditProfileUpdatingDetails: String { return L10n.tr("Localizable", "screen_edit_profile_updating_details") } + /// Verify this device to set up secure messaging. + internal static var screenIdentityConfirmationSubtitle: String { return L10n.tr("Localizable", "screen_identity_confirmation_subtitle") } + /// Confirm that it's you + internal static var screenIdentityConfirmationTitle: String { return L10n.tr("Localizable", "screen_identity_confirmation_title") } + /// Now you can read or send messages securely, and anyone you chat with can also trust this device. + internal static var screenIdentityConfirmedSubtitle: String { return L10n.tr("Localizable", "screen_identity_confirmed_subtitle") } + /// Device verified + internal static var screenIdentityConfirmedTitle: String { return L10n.tr("Localizable", "screen_identity_confirmed_title") } /// Are you sure you want to decline the invitation to join %1$@? internal static func screenInvitesDeclineChatMessage(_ p1: Any) -> String { return L10n.tr("Localizable", "screen_invites_decline_chat_message", String(describing: p1)) diff --git a/ElementX/Sources/Other/ProcessInfo.swift b/ElementX/Sources/Other/ProcessInfo.swift index 11fa261ac..d41b81144 100644 --- a/ElementX/Sources/Other/ProcessInfo.swift +++ b/ElementX/Sources/Other/ProcessInfo.swift @@ -34,10 +34,18 @@ extension ProcessInfo { false #endif } + + static var isRunningIntegrationTests: Bool { + #if DEBUG + processInfo.environment["IS_RUNNING_INTEGRATION_TESTS"] == "1" + #else + false + #endif + } /// Flag indicating whether the app is running the UI tests or unit tests. static var isRunningTests: Bool { - isRunningUITests || isRunningUnitTests + isRunningUITests || isRunningUnitTests || isRunningIntegrationTests } /// The identifier of the screen to be loaded when running UI tests. diff --git a/ElementX/Sources/Other/SwiftUI/Layout/FullscreenDialog.swift b/ElementX/Sources/Other/SwiftUI/Layout/FullscreenDialog.swift index 86edb78ff..08804e9bd 100644 --- a/ElementX/Sources/Other/SwiftUI/Layout/FullscreenDialog.swift +++ b/ElementX/Sources/Other/SwiftUI/Layout/FullscreenDialog.swift @@ -33,6 +33,8 @@ struct FullscreenDialog: View { /// The spacing between the content and the buttons. var spacing: CGFloat = 16 + var showBackgroundGradient = false + /// The main content shown at the top of the layout. @ViewBuilder var content: () -> Content /// The content shown at the bottom of the layout. @@ -62,23 +64,32 @@ struct FullscreenDialog: View { } .scrollBounceBehavior(.basedOnSize) .safeAreaInset(edge: .bottom) { - VStack(spacing: 0) { - bottomContent() - .readableFrame() - .padding(.horizontal, horizontalPadding) - .padding(.top, spacing) - .padding(.bottom, UIConstants.actionButtonBottomPadding) - - Spacer() - .frame(height: UIConstants.spacerHeight(in: geometry)) + if showBackgroundGradient { + adjustedBottomContent(geometry: geometry) + .background(Asset.Images.backgroundBottom.swiftUIImage.resizable().ignoresSafeArea()) + } else { + adjustedBottomContent(geometry: geometry) + .background() } - .background() } } } + private func adjustedBottomContent(geometry: GeometryProxy) -> some View { + VStack(spacing: 0) { + bottomContent() + .readableFrame() + .padding(.horizontal, horizontalPadding) + .padding(.top, spacing) + .padding(.bottom, UIConstants.actionButtonBottomPadding) + + Spacer() + .frame(height: UIConstants.spacerHeight(in: geometry)) + } + } + /// A continuously scrolling layout used for accessibility font sizes. - var accessibilityLayout: some View { + private var accessibilityLayout: some View { GeometryReader { geometry in ScrollView { VStack(spacing: 0) { diff --git a/ElementX/Sources/Other/SwiftUI/Views/HeroImage.swift b/ElementX/Sources/Other/SwiftUI/Views/HeroImage.swift index 54154e453..b9ca45e04 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/HeroImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/HeroImage.swift @@ -21,35 +21,61 @@ import SwiftUI /// takes a compound icon. If you would like to apply it to an SFSymbol, you can call /// the `heroImage()` modifier directly on the Image. struct HeroImage: View { + enum Style { + case normal + case positive + + var foregroundColor: Color { + switch self { + case .normal: + return .compound.iconPrimary + case .positive: + return .compound.iconSuccessPrimary + } + } + + var backgroundFillColor: Color { + switch self { + case .normal: + return .compound.bgSubtleSecondary + case .positive: + return .compound.bgSuccessSubtle + } + } + } + /// The icon that is shown. let icon: KeyPath + var style: Style = .normal var body: some View { CompoundIcon(icon, size: .custom(42), relativeTo: .title) - .modifier(HeroImageModifier()) + .modifier(HeroImageModifier(style: style)) } } extension Image { /// Styles the image for use as the main/top/hero screen icon. You should prefer /// the HeroImage component when possible, by using an icon from Compound. - func heroImage(insets: CGFloat = 16) -> some View { + func heroImage(insets: CGFloat = 16, style: HeroImage.Style = .normal) -> some View { resizable() .renderingMode(.template) .aspectRatio(contentMode: .fit) .scaledPadding(insets, relativeTo: .title) - .modifier(HeroImageModifier()) + .modifier(HeroImageModifier(style: style)) } } private struct HeroImageModifier: ViewModifier { + let style: HeroImage.Style + func body(content: Content) -> some View { content - .foregroundColor(.compound.iconPrimary) .scaledFrame(size: 70, relativeTo: .title) + .foregroundColor(style.foregroundColor) .background { RoundedRectangle(cornerRadius: 14) - .fill(Color.compound.bgSubtleSecondary) + .fill(style.backgroundFillColor) } .accessibilityHidden(true) } diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/View/RoundedLabelItem.swift b/ElementX/Sources/Other/SwiftUI/Views/RoundedLabelItem.swift similarity index 100% rename from ElementX/Sources/Screens/AnalyticsPromptScreen/View/RoundedLabelItem.swift rename to ElementX/Sources/Other/SwiftUI/Views/RoundedLabelItem.swift diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenCoordinator.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenCoordinator.swift index 2001538e5..44640cf05 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenCoordinator.swift @@ -85,7 +85,7 @@ final class LoginScreenCoordinator: CoordinatorProtocol { // MARK: - Private - private static let loadingIndicatorIdentifier = "LoginCoordinatorLoading" + private static let loadingIndicatorIdentifier = "\(LoginScreenCoordinatorAction.self)-Loading" private func startLoading(isInteractionBlocking: Bool) { if isInteractionBlocking { diff --git a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift index 844df8816..722966791 100644 --- a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift @@ -104,7 +104,7 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol { // MARK: - Private - private static let loadingIndicatorIdentifier = "SoftLogoutLoading" + private static let loadingIndicatorIdentifier = "\(SoftLogoutScreenCoordinator.self)-Loading" /// Show an activity indicator whilst loading. @MainActor private func startLoading() { diff --git a/ElementX/Sources/Screens/BlockedUsersScreen/BlockedUsersScreenViewModel.swift b/ElementX/Sources/Screens/BlockedUsersScreen/BlockedUsersScreenViewModel.swift index 53fd79094..ae6e25674 100644 --- a/ElementX/Sources/Screens/BlockedUsersScreen/BlockedUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/BlockedUsersScreen/BlockedUsersScreenViewModel.swift @@ -82,7 +82,7 @@ class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsers // MARK: Loading indicator - private static let loadingIndicatorIdentifier = "BlockedUsersLoading" + private static let loadingIndicatorIdentifier = "\(BlockedUsersScreenViewModel.self)-Loading" private func showLoadingIndicator() { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, diff --git a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift index 0ed65537e..3c89c82c6 100644 --- a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift +++ b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift @@ -90,7 +90,7 @@ final class BugReportScreenCoordinator: CoordinatorProtocol { // MARK: - Private - private static let loadingIndicatorIdentifier = "BugReportLoading" + private static let loadingIndicatorIdentifier = "\(BugReportScreenCoordinator.self)-Loading" private func startLoading(label: String = L10n.commonLoading, progressPublisher: CurrentValuePublisher) { parameters.userIndicatorController?.submitIndicator( diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift index 1b20d96aa..358d9a4a3 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift @@ -171,7 +171,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol // MARK: Loading indicator - private static let loadingIndicatorIdentifier = "CreateRoomLoading" + private static let loadingIndicatorIdentifier = "\(CreateRoomViewModel.self)-Loading" private func showLoadingIndicator() { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index 1d4d510c2..f5bbb4672 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -31,7 +31,6 @@ enum HomeScreenCoordinatorAction { case roomLeft(roomIdentifier: String) case presentSettingsScreen case presentFeedbackScreen - case presentSessionVerificationScreen case presentSecureBackupSettings case presentStartChatScreen case presentInvitesScreen @@ -74,8 +73,6 @@ final class HomeScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.presentFeedbackScreen) case .presentSettingsScreen: actionsSubject.send(.presentSettingsScreen) - case .presentSessionVerificationScreen: - actionsSubject.send(.presentSessionVerificationScreen) case .presentSecureBackupSettings: actionsSubject.send(.presentSecureBackupSettings) case .logout: diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 99aacd895..4ec962c03 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -22,7 +22,6 @@ enum HomeScreenViewModelAction { case presentRoom(roomIdentifier: String) case presentRoomDetails(roomIdentifier: String) case roomLeft(roomIdentifier: String) - case presentSessionVerificationScreen case presentSecureBackupSettings case presentSettingsScreen case presentFeedbackScreen @@ -39,9 +38,7 @@ enum HomeScreenViewAction { case confirmLeaveRoom(roomIdentifier: String) case showSettings case startChat - case verifySession case confirmRecoveryKey - case skipSessionVerification case skipRecoveryKeyConfirmation case updateVisibleItemRange(range: Range, isScrolling: Bool) case selectInvites @@ -74,7 +71,6 @@ enum HomeScreenRoomListMode: CustomStringConvertible { enum SecurityBannerMode { case none case dismissed - case sessionVerification case recoveryKeyConfirmation } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 72c959d7d..198cd2d46 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -70,11 +70,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol guard let self else { return } switch (securityState.verificationState, securityState.recoveryState) { - case (.unverified, _): - state.requiresExtraAccountSetup = true - if state.securityBannerMode != .dismissed { - state.securityBannerMode = .sessionVerification - } case (.verified, .disabled): state.requiresExtraAccountSetup = true state.securityBannerMode = .none @@ -134,12 +129,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol leaveRoom(roomId: roomIdentifier) case .showSettings: actionsSubject.send(.presentSettingsScreen) - case .verifySession: - actionsSubject.send(.presentSessionVerificationScreen) case .confirmRecoveryKey: actionsSubject.send(.presentSecureBackupSettings) - case .skipSessionVerification: - state.securityBannerMode = .dismissed case .skipRecoveryKeyConfirmation: state.securityBannerMode = .dismissed case .updateVisibleItemRange(let range, let isScrolling): diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift index 34bb49e0e..f44529404 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift @@ -103,16 +103,13 @@ struct HomeScreenContent: View { } @ViewBuilder - /// The session verification banner and invites button if either are needed. private var topSection: some View { VStack(spacing: 0) { if !context.isSearchFieldFocused { RoomListFiltersView(state: $context.filtersState) } - if context.viewState.securityBannerMode == .sessionVerification { - HomeScreenSessionVerificationBanner(context: context) - } else if context.viewState.securityBannerMode == .recoveryKeyConfirmation { + if context.viewState.securityBannerMode == .recoveryKeyConfirmation { HomeScreenRecoveryKeyConfirmationBanner(context: context) } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenSessionVerificationBanner.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenSessionVerificationBanner.swift deleted file mode 100644 index 681cade60..000000000 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenSessionVerificationBanner.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright 2023 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Combine -import SwiftUI - -struct HomeScreenSessionVerificationBanner: View { - @ObservedObject var context: HomeScreenViewModel.Context - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - VStack(alignment: .leading, spacing: 4) { - HStack(spacing: 16) { - Text(L10n.sessionVerificationBannerTitle) - .font(.compound.bodyLGSemibold) - .foregroundColor(.compound.textPrimary) - - Spacer() - - Button { - context.send(viewAction: .skipSessionVerification) - } label: { - Image(systemName: "xmark") - .foregroundColor(.compound.iconSecondary) - .frame(width: 12, height: 12) - } - } - Text(L10n.sessionVerificationBannerMessage) - .font(.compound.bodyMD) - .foregroundColor(.compound.textSecondary) - } - - Button(L10n.actionContinue) { - context.send(viewAction: .verifySession) - } - .frame(maxWidth: .infinity) - .buttonStyle(.compound(.primary, size: .medium)) - .accessibilityIdentifier(A11yIdentifiers.homeScreen.verificationBannerContinue) - } - .padding(16) - .background(Color.compound.bgSubtleSecondary) - .cornerRadius(14) - .padding(.horizontal, 16) - } -} - -struct HomeScreenSessionVerificationBanner_Previews: PreviewProvider, TestablePreview { - static let viewModel = buildViewModel() - - static var previews: some View { - HomeScreenSessionVerificationBanner(context: viewModel.context) - } - - static func buildViewModel() -> HomeScreenViewModel { - let clientProxy = ClientProxyMock(.init(userID: "@alice:example.com", - roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loading)))) - - let userSession = MockUserSession(clientProxy: clientProxy, - mediaProvider: MockMediaProvider(), - voiceMessageMediaManager: VoiceMessageMediaManagerMock()) - - return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, - selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userIndicatorController: ServiceLocator.shared.userIndicatorController) - } -} diff --git a/ElementX/Sources/Screens/MediaPickerScreen/CameraPicker.swift b/ElementX/Sources/Screens/MediaPickerScreen/CameraPicker.swift index cb1146c24..e7d254643 100644 --- a/ElementX/Sources/Screens/MediaPickerScreen/CameraPicker.swift +++ b/ElementX/Sources/Screens/MediaPickerScreen/CameraPicker.swift @@ -63,7 +63,7 @@ struct CameraPicker: UIViewControllerRepresentable { self.cameraPicker = cameraPicker } - private static let loadingIndicatorIdentifier = "CameraPickerLoadingIndicator" + private static let loadingIndicatorIdentifier = "\(CameraPicker.self)-Loading" func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { picker.delegate = nil diff --git a/ElementX/Sources/Screens/MediaPickerScreen/DocumentPicker.swift b/ElementX/Sources/Screens/MediaPickerScreen/DocumentPicker.swift index a8f717a33..62e5969bd 100644 --- a/ElementX/Sources/Screens/MediaPickerScreen/DocumentPicker.swift +++ b/ElementX/Sources/Screens/MediaPickerScreen/DocumentPicker.swift @@ -62,7 +62,7 @@ struct DocumentPicker: UIViewControllerRepresentable { documentPicker.callback(.cancel) } - private static let loadingIndicatorIdentifier = "DocumentPickerLoadingIndicator" + private static let loadingIndicatorIdentifier = "\(DocumentPicker.self)-Loading" func documentPicker(_ picker: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let url = urls.first else { diff --git a/ElementX/Sources/Screens/MediaPickerScreen/PhotoLibraryPicker.swift b/ElementX/Sources/Screens/MediaPickerScreen/PhotoLibraryPicker.swift index 9c8c3b0c0..33b2165a3 100644 --- a/ElementX/Sources/Screens/MediaPickerScreen/PhotoLibraryPicker.swift +++ b/ElementX/Sources/Screens/MediaPickerScreen/PhotoLibraryPicker.swift @@ -62,7 +62,7 @@ struct PhotoLibraryPicker: UIViewControllerRepresentable { // MARK: PHPickerViewControllerDelegate - private static let loadingIndicatorIdentifier = "PhotoLibraryPickerLoadingIndicator" + private static let loadingIndicatorIdentifier = "\(PhotoLibraryPicker.self)-Loading" func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { guard let provider = results.first?.itemProvider, diff --git a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift index 6b184fcec..a97369676 100644 --- a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift +++ b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift @@ -102,7 +102,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, } } - private static let loadingIndicatorIdentifier = "MediaUploadPreviewLoading" + private static let loadingIndicatorIdentifier = "\(MediaUploadPreviewScreenViewModel.self)-Loading" private func startLoading(progressPublisher: CurrentValuePublisher) { userIndicatorController.submitIndicator( diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift similarity index 100% rename from ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift rename to ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/AnalyticsPromptScreenCoordinator.swift diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenModels.swift b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/AnalyticsPromptScreenModels.swift similarity index 100% rename from ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenModels.swift rename to ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/AnalyticsPromptScreenModels.swift diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift similarity index 100% rename from ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift rename to ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/AnalyticsPromptScreenViewModel.swift diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenViewModelProtocol.swift b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/AnalyticsPromptScreenViewModelProtocol.swift similarity index 100% rename from ElementX/Sources/Screens/AnalyticsPromptScreen/AnalyticsPromptScreenViewModelProtocol.swift rename to ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/AnalyticsPromptScreenViewModelProtocol.swift diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift similarity index 92% rename from ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift rename to ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift index 785a97e6e..ade52f248 100644 --- a/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift +++ b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift @@ -21,7 +21,7 @@ struct AnalyticsPromptScreen: View { @ObservedObject var context: AnalyticsPromptScreenViewModel.Context var body: some View { - FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding) { + FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, showBackgroundGradient: true) { mainContent } bottomContent: { buttons @@ -30,6 +30,7 @@ struct AnalyticsPromptScreen: View { .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) .navigationBarHidden(true) .navigationBarBackButtonHidden(true) + .interactiveDismissDisabled() } /// The main content of the screen that is shown inside the scroll view. @@ -88,12 +89,9 @@ struct AnalyticsPromptScreen: View { /// The stack of enable/disable buttons. private var buttons: some View { VStack(spacing: 16) { - Button { context.send(viewAction: .enable) } label: { - Text(L10n.actionOk) - .font(.compound.bodyLGSemibold) - } - .buttonStyle(.compound(.primary)) - .accessibilityIdentifier(A11yIdentifiers.analyticsPromptScreen.enable) + Button(L10n.actionOk) { context.send(viewAction: .enable) } + .buttonStyle(.compound(.primary)) + .accessibilityIdentifier(A11yIdentifiers.analyticsPromptScreen.enable) Button { context.send(viewAction: .disable) } label: { Text(L10n.actionNotNow) diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenCoordinator.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenCoordinator.swift new file mode 100644 index 000000000..3e953e9a3 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenCoordinator.swift @@ -0,0 +1,70 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// periphery:ignore:all - this is just a identityConfirmation remove this comment once generating the final file + +import Combine +import SwiftUI + +struct IdentityConfirmationScreenCoordinatorParameters { + let userSession: UserSessionProtocol + let appSettings: AppSettings + let userIndicatorController: UserIndicatorControllerProtocol +} + +enum IdentityConfirmationScreenCoordinatorAction { + case otherDevice + case recoveryKey +} + +final class IdentityConfirmationScreenCoordinator: CoordinatorProtocol { + private let parameters: IdentityConfirmationScreenCoordinatorParameters + private let viewModel: IdentityConfirmationScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: IdentityConfirmationScreenCoordinatorParameters) { + self.parameters = parameters + + viewModel = IdentityConfirmationScreenViewModel(userSession: parameters.userSession, + appSettings: parameters.appSettings, + userIndicatorController: parameters.userIndicatorController) + } + + func start() { + viewModel.actionsPublisher.sink { [weak self] action in + guard let self else { return } + + MXLog.info("Coordinator: received view model action: \(action)") + switch action { + case .otherDevice: + actionsSubject.send(.otherDevice) + case .recoveryKey: + actionsSubject.send(.recoveryKey) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(IdentityConfirmationScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenModels.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenModels.swift new file mode 100644 index 000000000..e358b78c1 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenModels.swift @@ -0,0 +1,37 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +enum IdentityConfirmationScreenViewModelAction { + case otherDevice + case recoveryKey +} + +struct IdentityConfirmationScreenViewState: BindableState { + enum Mode { + case recoveryOnly + case recoveryAndVerification + } + + var mode = Mode.recoveryOnly + let learnMoreURL: URL +} + +enum IdentityConfirmationScreenViewAction { + case otherDevice + case recoveryKey +} diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModel.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModel.swift new file mode 100644 index 000000000..0cdf8f799 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModel.swift @@ -0,0 +1,95 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +typealias IdentityConfirmationScreenViewModelType = StateStoreViewModel + +class IdentityConfirmationScreenViewModel: IdentityConfirmationScreenViewModelType, IdentityConfirmationScreenViewModelProtocol { + private let userSession: UserSessionProtocol + private let userIndicatorController: UserIndicatorControllerProtocol + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(userSession: UserSessionProtocol, appSettings: AppSettings, userIndicatorController: UserIndicatorControllerProtocol) { + self.userSession = userSession + self.userIndicatorController = userIndicatorController + + super.init(initialViewState: IdentityConfirmationScreenViewState(learnMoreURL: appSettings.encryptionURL)) + + userSession.sessionSecurityStatePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] state in + Task { + await self?.updateWithSessionSecurityState(state) + } + } + .store(in: &cancellables) + + Task { + await updateWithSessionSecurityState(userSession.sessionSecurityStatePublisher.value) + } + } + + // MARK: - Public + + override func process(viewAction: IdentityConfirmationScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + switch viewAction { + case .otherDevice: + actionsSubject.send(.otherDevice) + case .recoveryKey: + actionsSubject.send(.recoveryKey) + } + } + + // MARK: - Private + + private func updateWithSessionSecurityState(_ sessionSecurityState: SessionSecurityState) async { + if sessionSecurityState.verificationState == .unknown { + showLoadingIndicator() + } else { + hideLoadingIndicator() + } + + guard sessionSecurityState.verificationState == .unverified else { + return + } + + guard case let .success(isOnlyDeviceLeft) = await userSession.clientProxy.isOnlyDeviceLeft() else { + return + } + + state.mode = isOnlyDeviceLeft ? .recoveryOnly : .recoveryAndVerification + } + + private static let loadingIndicatorIdentifier = "\(IdentityConfirmationScreenViewModel.self)-Loading" + + private func showLoadingIndicator() { + userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, + type: .modal, + title: L10n.commonLoading, + persistent: true)) + } + + private func hideLoadingIndicator() { + userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) + } +} diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModelProtocol.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModelProtocol.swift new file mode 100644 index 000000000..fff1cb67e --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModelProtocol.swift @@ -0,0 +1,23 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine + +@MainActor +protocol IdentityConfirmationScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: IdentityConfirmationScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift new file mode 100644 index 000000000..d4dc6a0cb --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift @@ -0,0 +1,107 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Compound +import SwiftUI + +struct IdentityConfirmationScreen: View { + @ObservedObject var context: IdentityConfirmationScreenViewModel.Context + + var body: some View { + FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding) { + screenHeader + } bottomContent: { + actionButtons + } + .background() + .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) + .interactiveDismissDisabled() + } + + // MARK: - Private + + @ViewBuilder + private var screenHeader: some View { + VStack(spacing: 0) { + HeroImage(icon: \.lockSolid) + .padding(.bottom, 16) + + Text(L10n.screenIdentityConfirmationTitle) + .font(.compound.headingMDBold) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textPrimary) + .padding(.bottom, 8) + + Text(L10n.screenIdentityConfirmationSubtitle) + .font(.compound.bodyMD) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textSecondary) + + Button(L10n.actionLearnMore) { + UIApplication.shared.open(context.viewState.learnMoreURL) + } + .buttonStyle(.compound(.plain)) + .padding(.top, 16) + } + } + + @ViewBuilder + private var actionButtons: some View { + VStack(spacing: 32) { + switch context.viewState.mode { + case .recoveryOnly: + Button(L10n.screenSessionVerificationEnterRecoveryKey) { + context.send(viewAction: .recoveryKey) + } + .buttonStyle(.compound(.primary)) + + case .recoveryAndVerification: + Button(L10n.actionStartVerification) { + context.send(viewAction: .otherDevice) + } + .buttonStyle(.compound(.primary)) + + Button(L10n.screenSessionVerificationEnterRecoveryKey) { + context.send(viewAction: .recoveryKey) + } + .buttonStyle(.compound(.plain)) + } + } + } +} + +// MARK: - Previews + +struct IdentityConfirmationScreen_Previews: PreviewProvider, TestablePreview { + static var previews: some View { + NavigationStack { + IdentityConfirmationScreen(context: viewModel.context) + } + } + + private static var viewModel: IdentityConfirmationScreenViewModel { + let userSession = MockUserSession(clientProxy: ClientProxyMock(.init(userID: "@user:example.com", + roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))))), + mediaProvider: MockMediaProvider(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock()) + + return IdentityConfirmationScreenViewModel(userSession: userSession, + appSettings: ServiceLocator.shared.settings, + userIndicatorController: ServiceLocator.shared.userIndicatorController) + } +} diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenCoordinator.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenCoordinator.swift new file mode 100644 index 000000000..4d33fcc77 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenCoordinator.swift @@ -0,0 +1,61 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// periphery:ignore:all - this is just a identityConfirmed remove this comment once generating the final file + +import Combine +import SwiftUI + +struct IdentityConfirmedScreenCoordinatorParameters { } + +enum IdentityConfirmedScreenCoordinatorAction { + case done +} + +final class IdentityConfirmedScreenCoordinator: CoordinatorProtocol { + private let parameters: IdentityConfirmedScreenCoordinatorParameters + private let viewModel: IdentityConfirmedScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: IdentityConfirmedScreenCoordinatorParameters) { + self.parameters = parameters + + viewModel = IdentityConfirmedScreenViewModel() + } + + func start() { + viewModel.actionsPublisher.sink { [weak self] action in + MXLog.info("Coordinator: received view model action: \(action)") + + guard let self else { return } + switch action { + case .done: + self.actionsSubject.send(.done) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(IdentityConfirmedScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenModels.swift similarity index 73% rename from ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift rename to ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenModels.swift index 13d293538..2861edbdc 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenModels.swift @@ -1,5 +1,5 @@ // -// Copyright 2023 New Vector Ltd +// Copyright 2022 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,7 +16,12 @@ import Foundation -struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Equatable { - let id: TimelineItemIdentifier - let isSessionVerified: Bool +enum IdentityConfirmedScreenViewModelAction { + case done +} + +struct IdentityConfirmedScreenViewState: BindableState { } + +enum IdentityConfirmedScreenViewAction { + case done } diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenViewModel.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenViewModel.swift new file mode 100644 index 000000000..4def13f96 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenViewModel.swift @@ -0,0 +1,42 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +typealias IdentityConfirmedScreenViewModelType = StateStoreViewModel + +class IdentityConfirmedScreenViewModel: IdentityConfirmedScreenViewModelType, IdentityConfirmedScreenViewModelProtocol { + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init() { + super.init(initialViewState: .init()) + } + + // MARK: - Public + + override func process(viewAction: IdentityConfirmedScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + + switch viewAction { + case .done: + actionsSubject.send(.done) + } + } +} diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenViewModelProtocol.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenViewModelProtocol.swift new file mode 100644 index 000000000..6bb2969ea --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/IdentityConfirmedScreenViewModelProtocol.swift @@ -0,0 +1,23 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine + +@MainActor +protocol IdentityConfirmedScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: IdentityConfirmedScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/View/IdentityConfirmedScreen.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/View/IdentityConfirmedScreen.swift new file mode 100644 index 000000000..e5f0924c5 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmedScreen/View/IdentityConfirmedScreen.swift @@ -0,0 +1,75 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Compound +import SwiftUI + +struct IdentityConfirmedScreen: View { + @ObservedObject var context: IdentityConfirmedScreenViewModel.Context + + var body: some View { + FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding) { + screenHeader + } bottomContent: { + actionButtons + } + .background() + .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) + .interactiveDismissDisabled() + } + + // MARK: - Private + + @ViewBuilder + private var screenHeader: some View { + VStack(spacing: 0) { + HeroImage(icon: \.checkCircle, style: .positive) + .padding(.bottom, 16) + + Text(L10n.screenIdentityConfirmedTitle) + .font(.compound.headingMDBold) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textPrimary) + .padding(.bottom, 8) + + Text(L10n.screenIdentityConfirmedSubtitle) + .font(.compound.bodyMD) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textSecondary) + } + } + + @ViewBuilder + private var actionButtons: some View { + Button(L10n.actionContinue) { + context.send(viewAction: .done) + } + .buttonStyle(.compound(.primary)) + } +} + +// MARK: - Previews + +struct IdentityConfirmedScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = IdentityConfirmedScreenViewModel() + static var previews: some View { + NavigationStack { + IdentityConfirmedScreen(context: viewModel.context) + } + } +} diff --git a/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenCoordinator.swift b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenCoordinator.swift new file mode 100644 index 000000000..07e958986 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenCoordinator.swift @@ -0,0 +1,59 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +struct NotificationPermissionsScreenCoordinatorParameters { + let notificationManager: NotificationManagerProtocol +} + +enum NotificationPermissionsScreenCoordinatorAction { + case done +} + +final class NotificationPermissionsScreenCoordinator: CoordinatorProtocol { + private var viewModel: NotificationPermissionsScreenViewModelProtocol + private let actionsSubject: PassthroughSubject = .init() + private var cancellables = Set() + + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: NotificationPermissionsScreenCoordinatorParameters) { + viewModel = NotificationPermissionsScreenViewModel(notificationManager: parameters.notificationManager) + } + + // MARK: - Public + + func start() { + viewModel.actionsPublisher + .sink { [weak self] action in + guard let self else { return } + + switch action { + case .done: + actionsSubject.send(.done) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(NotificationPermissionsScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenModels.swift b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenModels.swift new file mode 100644 index 000000000..d841d9f1d --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenModels.swift @@ -0,0 +1,28 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +enum NotificationPermissionsScreenViewAction { + case enable + case notNow +} + +enum NotificationPermissionsScreenViewModelAction { + case done +} + +struct NotificationPermissionsScreenViewState: BindableState { } diff --git a/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenViewModel.swift b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenViewModel.swift new file mode 100644 index 000000000..b2f957a07 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenViewModel.swift @@ -0,0 +1,48 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +typealias NotificationPermissionsScreenViewModelType = StateStoreViewModel + +class NotificationPermissionsScreenViewModel: NotificationPermissionsScreenViewModelType, NotificationPermissionsScreenViewModelProtocol { + private let notificationManager: NotificationManagerProtocol + + private var actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(notificationManager: NotificationManagerProtocol) { + self.notificationManager = notificationManager + + super.init(initialViewState: .init()) + } + + // MARK: - Public + + override func process(viewAction: NotificationPermissionsScreenViewAction) { + switch viewAction { + case .enable: + notificationManager.requestAuthorization() + + actionsSubject.send(.done) + case .notNow: + actionsSubject.send(.done) + } + } +} diff --git a/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenViewModelProtocol.swift b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenViewModelProtocol.swift new file mode 100644 index 000000000..016d53968 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/NotificationPermissionsScreenViewModelProtocol.swift @@ -0,0 +1,23 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine + +@MainActor +protocol NotificationPermissionsScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: NotificationPermissionsScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/View/NotificationPermissionsScreen.swift b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/View/NotificationPermissionsScreen.swift new file mode 100644 index 000000000..7e5cafc91 --- /dev/null +++ b/ElementX/Sources/Screens/Onboarding/NotificationPermissionsScreen/View/NotificationPermissionsScreen.swift @@ -0,0 +1,77 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +/// A prompt that asks the user whether they would like to enable Analytics or not. +struct NotificationPermissionsScreen: View { + @ObservedObject var context: NotificationPermissionsScreenViewModel.Context + + var body: some View { + FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, showBackgroundGradient: true) { + mainContent + } bottomContent: { + buttons + } + .background() + .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) + .interactiveDismissDisabled() + } + + /// The main content of the screen that is shown inside the scroll view. + private var mainContent: some View { + VStack(spacing: 8) { + HeroImage(icon: \.notificationsSolid) + .padding(.bottom, 8) + + Text(L10n.screenNotificationOptinTitle) + .font(.compound.headingMDBold) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textPrimary) + + Text(L10n.screenNotificationOptinSubtitle) + .font(.compound.bodyMD) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textSecondary) + + Asset.Images.notificationsPromptGraphic.swiftUIImage.resizable().aspectRatio(contentMode: .fit) + } + } + + private var buttons: some View { + VStack(spacing: 16) { + Button(L10n.actionOk) { context.send(viewAction: .enable) } + .buttonStyle(.compound(.primary)) + + Button { context.send(viewAction: .notNow) } label: { + Text(L10n.actionNotNow) + .font(.compound.bodyLGSemibold) + .padding(14) + } + } + } +} + +// MARK: - Previews + +struct NotificationPermissionsScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = NotificationPermissionsScreenViewModel(notificationManager: NotificationManagerMock()) + static var previews: some View { + NotificationPermissionsScreen(context: viewModel.context) + } +} diff --git a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenCoordinator.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenCoordinator.swift similarity index 88% rename from ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenCoordinator.swift rename to ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenCoordinator.swift index 4e664470f..bd9465508 100644 --- a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenCoordinator.swift @@ -18,13 +18,11 @@ import Combine import SwiftUI enum SessionVerificationScreenCoordinatorAction { - case recoveryKey case done } struct SessionVerificationScreenCoordinatorParameters { let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol - let recoveryState: SecureBackupRecoveryState } final class SessionVerificationScreenCoordinator: CoordinatorProtocol { @@ -38,8 +36,7 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol { } init(parameters: SessionVerificationScreenCoordinatorParameters) { - viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy, - recoveryState: parameters.recoveryState) + viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy) } // MARK: - Public @@ -50,8 +47,6 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol { guard let self else { return } switch action { - case .recoveryKey: - actionsSubject.send(.recoveryKey) case .finished: actionsSubject.send(.done) } @@ -59,6 +54,10 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol { .store(in: &cancellables) } + func stop() { + viewModel.stop() + } + func toPresentable() -> AnyView { AnyView(SessionVerificationScreen(context: viewModel.context)) } diff --git a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenModels.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenModels.swift similarity index 97% rename from ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenModels.swift rename to ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenModels.swift index b2c5c9da7..dd0a865fb 100644 --- a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenModels.swift +++ b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenModels.swift @@ -17,13 +17,10 @@ import Foundation enum SessionVerificationScreenViewModelAction { - case recoveryKey case finished } struct SessionVerificationScreenViewState: BindableState { - let showRecoveryOption: Bool - var verificationState: SessionVerificationScreenStateMachine.State = .initial var title: String? { @@ -86,11 +83,9 @@ struct SessionVerificationScreenViewState: BindableState { } enum SessionVerificationScreenViewAction { - case recoveryKey case requestVerification case startSasVerification case restart case accept case decline - case close } diff --git a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenStateMachine.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenStateMachine.swift similarity index 100% rename from ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenStateMachine.swift rename to ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenStateMachine.swift diff --git a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenViewModel.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModel.swift similarity index 88% rename from ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenViewModel.swift rename to ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModel.swift index 47d8b2f18..c2f9c615b 100644 --- a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenViewModel.swift +++ b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModel.swift @@ -31,13 +31,12 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess } init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol, - recoveryState: SecureBackupRecoveryState, verificationState: SessionVerificationScreenStateMachine.State = .initial) { self.sessionVerificationControllerProxy = sessionVerificationControllerProxy stateMachine = SessionVerificationScreenStateMachine() - super.init(initialViewState: .init(showRecoveryOption: recoveryState == .incomplete, verificationState: verificationState)) + super.init(initialViewState: .init(verificationState: verificationState)) setupStateMachine() @@ -71,23 +70,12 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess override func process(viewAction: SessionVerificationScreenViewAction) { switch viewAction { - case .recoveryKey: - actionsSubject.send(.recoveryKey) case .requestVerification: stateMachine.processEvent(.requestVerification) case .startSasVerification: stateMachine.processEvent(.startSasVerification) case .restart: stateMachine.processEvent(.restart) - case .close: - guard stateMachine.state == .initial || - stateMachine.state == .verified || - stateMachine.state == .cancelled else { - stateMachine.processEvent(.cancel) - return - } - - actionsSubject.send(.finished) case .accept: stateMachine.processEvent(.acceptChallenge) case .decline: @@ -95,6 +83,14 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess } } + func stop() { + let uncancellableStates: [SessionVerificationScreenStateMachine.State] = [.initial, .verified, .cancelled] + + if !uncancellableStates.contains(stateMachine.state) { + stateMachine.processEvent(.cancel) + } + } + // MARK: - Private private func setupStateMachine() { @@ -115,11 +111,7 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess case (_, .cancel, .cancelling): cancelVerification() case (_, _, .verified): - // Dismiss the success screen automatically. - Task { - try? await Task.sleep(for: .seconds(2)) - self.actionsSubject.send(.finished) - } + actionsSubject.send(.finished) default: break } diff --git a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenViewModelProtocol.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModelProtocol.swift similarity index 97% rename from ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenViewModelProtocol.swift rename to ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModelProtocol.swift index c145f0a7d..e94a5738a 100644 --- a/ElementX/Sources/Screens/SessionVerificationScreen/SessionVerificationScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModelProtocol.swift @@ -20,4 +20,6 @@ import Combine protocol SessionVerificationScreenViewModelProtocol { var actions: AnyPublisher { get } var context: SessionVerificationViewModelType.Context { get } + + func stop() } diff --git a/ElementX/Sources/Screens/SessionVerificationScreen/View/SessionVerificationScreen.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift similarity index 78% rename from ElementX/Sources/Screens/SessionVerificationScreen/View/SessionVerificationScreen.swift rename to ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift index cbb1af994..f1aefbc07 100644 --- a/ElementX/Sources/Screens/SessionVerificationScreen/View/SessionVerificationScreen.swift +++ b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift @@ -20,23 +20,18 @@ struct SessionVerificationScreen: View { @ObservedObject var context: SessionVerificationScreenViewModel.Context var body: some View { - NavigationStack { - ScrollView { - VStack(spacing: 32) { - screenHeader - Spacer() - mainContent - } - .padding(.horizontal, 16) - .padding(.top, 24) - .frame(maxWidth: .infinity) - .navigationBarTitleDisplayMode(.inline) - .toolbar { toolbarContent } + FullscreenDialog { + VStack(spacing: 32) { + screenHeader + Spacer() + mainContent } - .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) - .safeAreaInset(edge: .bottom) { actionButtons.padding() } + } bottomContent: { + actionButtons } - .interactiveDismissDisabled() // Make sure dismissal goes through the state machine(s). + .background() + .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) + .interactiveDismissDisabled() } // MARK: - Private @@ -44,7 +39,7 @@ struct SessionVerificationScreen: View { private var headerImageName: String { switch context.viewState.verificationState { case .initial: - return "macbook.and.iphone" + return "lock" case .cancelled: return "exclamationmark.shield" case .requestingVerification: @@ -71,19 +66,24 @@ struct SessionVerificationScreen: View { @ViewBuilder private var screenHeader: some View { VStack(spacing: 0) { - Image(systemName: headerImageName) - .heroImage() - .padding(.bottom, 16) + if context.viewState.verificationState == .initial { + HeroImage(icon: \.lockSolid) + .padding(.bottom, 16) + } else { + Image(systemName: headerImageName) + .heroImage() + .padding(.bottom, 16) + } Text(context.viewState.title ?? "") - .font(.title2.bold()) + .font(.compound.headingMDBold) .multilineTextAlignment(.center) .foregroundColor(.compound.textPrimary) .padding(.bottom, 8) .accessibilityIdentifier(context.viewState.titleAccessibilityIdentifier) Text(context.viewState.message) - .font(.subheadline) + .font(.compound.bodyMD) .multilineTextAlignment(.center) .foregroundColor(.compound.textSecondary) } @@ -134,14 +134,6 @@ struct SessionVerificationScreen: View { } .buttonStyle(.compound(.primary)) .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification) - - if context.viewState.showRecoveryOption { - Button(L10n.screenSessionVerificationEnterRecoveryKey) { - context.send(viewAction: .recoveryKey) - } - .buttonStyle(.compound(.plain)) - .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.enterRecoveryKey) - } } case .cancelled: Button(L10n.actionRetry) { @@ -157,9 +149,9 @@ struct SessionVerificationScreen: View { .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.startSasVerification) case .showingChallenge: - VStack(spacing: 30) { - Button { context.send(viewAction: .accept) } label: { - Label(L10n.screenSessionVerificationTheyMatch, systemImage: "checkmark") + VStack(spacing: 32) { + Button(L10n.screenSessionVerificationTheyMatch) { + context.send(viewAction: .accept) } .buttonStyle(.compound(.primary)) .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge) @@ -172,12 +164,12 @@ struct SessionVerificationScreen: View { } case .acceptingChallenge: - VStack(spacing: 30) { + VStack(spacing: 32) { Button { context.send(viewAction: .accept) } label: { HStack(spacing: 16) { ProgressView() .tint(.compound.textOnSolidPrimary) - Label(L10n.screenSessionVerificationTheyMatch, systemImage: "checkmark") + Text(L10n.screenSessionVerificationTheyMatch) } } .buttonStyle(.compound(.primary)) @@ -197,16 +189,6 @@ struct SessionVerificationScreen: View { } } - @ToolbarContentBuilder - private var toolbarContent: some ToolbarContent { - ToolbarItem(placement: .cancellationAction) { - Button(L10n.actionCancel) { - context.send(viewAction: .close) - } - .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.close) - } - } - struct EmojiView: View { let emoji: SessionVerificationEmoji @@ -244,7 +226,6 @@ struct SessionVerification_Previews: PreviewProvider, TestablePreview { static func sessionVerificationScreen(state: SessionVerificationScreenStateMachine.State) -> some View { let viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(), - recoveryState: .incomplete, verificationState: state) return SessionVerificationScreen(context: viewModel.context) diff --git a/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenCoordinator.swift b/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenCoordinator.swift index 1edc27a07..f55d79fba 100644 --- a/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenCoordinator.swift +++ b/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenCoordinator.swift @@ -82,7 +82,7 @@ final class ReportContentScreenCoordinator: CoordinatorProtocol { // MARK: - Private - private static let loadingIndicatorIdentifier = "ReportContentLoading" + private static let loadingIndicatorIdentifier = "\(ReportContentScreenCoordinator.self)-Loading" private func startLoading() { parameters.userIndicatorController.submitIndicator( diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift index 1c2ffbc6f..7e5c305a8 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift @@ -171,7 +171,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro // MARK: Loading indicator - private static let loadingIndicatorIdentifier = "RoomMemberLoading" + private static let loadingIndicatorIdentifier = "\(RoomMemberDetailsScreenViewModel.self)-Loading" private func showMemberLoadingIndicator() { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift index ef557e25b..b37f8bcaf 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift @@ -97,9 +97,8 @@ struct TimelineItemPlainStylerView: View { HStack(spacing: 8) { TimelineSenderAvatarView(timelineItem: timelineItem) Text(timelineItem.sender.displayName ?? timelineItem.sender.id) - .font(.subheadline) + .font(.compound.bodyMDSemibold) .foregroundColor(.compound.decorativeColor(for: timelineItem.sender.id).text) - .fontWeight(.semibold) .lineLimit(1) } .onTapGesture { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift deleted file mode 100644 index 85633e5e1..000000000 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright 2023 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Compound -import SwiftUI - -/// The item shown when all previous items are un-decryptable due to -/// key backup not yet being supported in the app. -struct EncryptedHistoryRoomTimelineView: View { - let timelineItem: EncryptedHistoryRoomTimelineItem - - var body: some View { - Label { - Text(title) - .font(.compound.bodyMDSemibold) - .foregroundColor(.compound.textInfoPrimary) - } icon: { - CompoundIcon(\.infoSolid, size: .small, relativeTo: .compound.bodyMDSemibold) - .foregroundColor(.compound.iconInfoPrimary) - } - .labelStyle(EncryptedHistoryLabelStyle()) - .padding(16) - .background { - RoundedRectangle(cornerRadius: 8) - .fill(Color.compound.bgInfoSubtle) - RoundedRectangle(cornerRadius: 8) - .stroke(Color.compound.borderInfoSubtle) - } - .frame(maxWidth: .infinity) - .padding(.horizontal, 8) - .padding(.vertical, 16) - } - - private var title: String { - timelineItem.isSessionVerified ? L10n.screenRoomEncryptedHistoryBanner : L10n.screenRoomEncryptedHistoryBannerUnverified - } -} - -private struct EncryptedHistoryLabelStyle: LabelStyle { - func makeBody(configuration: Configuration) -> some View { - HStack(alignment: .top, spacing: 16) { - configuration.icon - configuration.title - } - } -} - -struct EncryptedHistoryRoomTimelineView_Previews: PreviewProvider, TestablePreview { - static var previews: some View { - VStack(spacing: 8) { - EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: true)) - EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: false)) - } - } -} diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/SecureBackupKeyBackupScreenViewModel.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/SecureBackupKeyBackupScreenViewModel.swift index 6572c7a58..0f12612ba 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/SecureBackupKeyBackupScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/SecureBackupKeyBackupScreenViewModel.swift @@ -86,7 +86,7 @@ class SecureBackupKeyBackupScreenViewModel: SecureBackupKeyBackupScreenViewModel } } - private static let loadingIndicatorIdentifier = "SecureBackupKeyBackupScreenLoading" + private static let loadingIndicatorIdentifier = "\(SecureBackupKeyBackupScreenViewModel.self)-Loading" private func showLoadingIndicator() { userIndicatorController?.submitIndicator(.init(id: Self.loadingIndicatorIdentifier, type: .modal, title: L10n.commonLoading, persistent: true)) diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/View/SecureBackupKeyBackupScreen.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/View/SecureBackupKeyBackupScreen.swift index a27e3d632..10e5d8652 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/View/SecureBackupKeyBackupScreen.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/View/SecureBackupKeyBackupScreen.swift @@ -22,12 +22,21 @@ struct SecureBackupKeyBackupScreen: View { @ObservedObject var context: SecureBackupKeyBackupScreenViewModel.Context var body: some View { - mainContent - .padding() - .interactiveDismissDisabled() - .toolbar { toolbar } - .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) - .alert(item: $context.alertInfo) + FullscreenDialog { + mainContent + } bottomContent: { + Button(role: .destructive) { + context.send(viewAction: .toggleBackup) + } label: { + Text(L10n.screenChatBackupKeyBackupActionDisable) + } + .buttonStyle(.compound(.primary)) + } + .background() + .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) + .interactiveDismissDisabled() + .toolbar { toolbar } + .alert(item: $context.alertInfo) } @ViewBuilder @@ -71,15 +80,6 @@ struct SecureBackupKeyBackupScreen: View { .foregroundColor(.compound.iconCriticalPrimary) } } - - Spacer() - - Button(role: .destructive) { - context.send(viewAction: .toggleBackup) - } label: { - Text(L10n.screenChatBackupKeyBackupActionDisable) - } - .buttonStyle(.compound(.primary)) } } diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift index 1e29b4e63..817f5eeff 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift @@ -22,26 +22,21 @@ struct SecureBackupLogoutConfirmationScreen: View { @ObservedObject var context: SecureBackupLogoutConfirmationScreenViewModel.Context var body: some View { - NavigationStack { - ScrollView { - VStack(spacing: 16) { - header - content - } - .padding() + FullscreenDialog { + VStack(spacing: 16) { + HeroImage(icon: \.keyOffSolid) + content } - .toolbar { toolbar } - .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) - .safeAreaInset(edge: .bottom) { footer.padding() } - .alert(item: $context.alertInfo) + .padding() + } bottomContent: { + footer.padding() } + .toolbar { toolbar } + .background() + .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) + .alert(item: $context.alertInfo) } - - @ViewBuilder - private var header: some View { - HeroImage(icon: \.keyOffSolid) - } - + @ViewBuilder private var content: some View { Text(title) @@ -62,21 +57,23 @@ struct SecureBackupLogoutConfirmationScreen: View { @ViewBuilder private var footer: some View { - if context.viewState.mode == .saveRecoveryKey { - Button { - context.send(viewAction: .settings) + VStack(spacing: 16.0) { + if context.viewState.mode == .saveRecoveryKey { + Button { + context.send(viewAction: .settings) + } label: { + Text(L10n.commonSettings) + } + .buttonStyle(.compound(.primary)) + } + + Button(role: .destructive) { + context.send(viewAction: .logout) } label: { - Text(L10n.commonSettings) + Text(L10n.actionSignout) } .buttonStyle(.compound(.primary)) } - - Button(role: .destructive) { - context.send(viewAction: .logout) - } label: { - Text(L10n.actionSignout) - } - .buttonStyle(.compound(.primary)) } @ToolbarContentBuilder @@ -88,7 +85,7 @@ struct SecureBackupLogoutConfirmationScreen: View { } } - var title: String { + private var title: String { switch context.viewState.mode { case .saveRecoveryKey: return L10n.screenSignoutSaveRecoveryKeyTitle @@ -99,7 +96,7 @@ struct SecureBackupLogoutConfirmationScreen: View { } } - var subtitle: String { + private var subtitle: String { switch context.viewState.mode { case .saveRecoveryKey: return L10n.screenSignoutSaveRecoveryKeySubtitle diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenCoordinator.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenCoordinator.swift index 543767f65..28dbf0f38 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenCoordinator.swift @@ -20,6 +20,7 @@ import SwiftUI struct SecureBackupRecoveryKeyScreenCoordinatorParameters { let secureBackupController: SecureBackupControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol + let isModallyPresented: Bool } enum SecureBackupRecoveryKeyScreenCoordinatorAction { @@ -40,7 +41,8 @@ final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol { init(parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters) { viewModel = SecureBackupRecoveryKeyScreenViewModel(secureBackupController: parameters.secureBackupController, - userIndicatorController: parameters.userIndicatorController) + userIndicatorController: parameters.userIndicatorController, + isModallyPresented: parameters.isModallyPresented) } func start() { diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenModels.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenModels.swift index 44f60f78f..b5d9e9273 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenModels.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenModels.swift @@ -28,6 +28,9 @@ enum SecureBackupRecoveryKeyScreenViewMode { } struct SecureBackupRecoveryKeyScreenViewState: BindableState { + /// Whether the screen is presented modally or within a navigation stack. + var isModallyPresented: Bool + let mode: SecureBackupRecoveryKeyScreenViewMode var recoveryKey: String? diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenViewModel.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenViewModel.swift index 53175d65e..08a19cb43 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenViewModel.swift @@ -28,11 +28,15 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM actionsSubject.eraseToAnyPublisher() } - init(secureBackupController: SecureBackupControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol) { + init(secureBackupController: SecureBackupControllerProtocol, + userIndicatorController: UserIndicatorControllerProtocol, + isModallyPresented: Bool) { self.secureBackupController = secureBackupController self.userIndicatorController = userIndicatorController - super.init(initialViewState: .init(mode: secureBackupController.recoveryState.value.viewMode, bindings: .init())) + super.init(initialViewState: .init(isModallyPresented: isModallyPresented, + mode: secureBackupController.recoveryState.value.viewMode, + bindings: .init())) secureBackupController.recoveryState .receive(on: DispatchQueue.main) diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift index 666e19031..a7cae0abd 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift @@ -24,7 +24,7 @@ struct SecureBackupRecoveryKeyScreen: View { private let textFieldIdentifier = "textFieldIdentifier" var body: some View { - ScrollView { + FullscreenDialog { ScrollViewReader { reader in mainContent .padding(16) @@ -33,17 +33,14 @@ struct SecureBackupRecoveryKeyScreen: View { reader.scrollTo(textFieldIdentifier) } } - } - .safeAreaInset(edge: .bottom) { + } bottomContent: { footer - .padding([.horizontal, .bottom], 16) - .padding(.top, 8) - .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) } - .interactiveDismissDisabled() .toolbar { toolbar } .toolbar(.visible, for: .navigationBar) - .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) + .background() + .environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault)) + .interactiveDismissDisabled() .alert(item: $context.alertInfo) } @@ -101,7 +98,7 @@ struct SecureBackupRecoveryKeyScreen: View { @ToolbarContentBuilder private var toolbar: some ToolbarContent { - if context.viewState.recoveryKey == nil { + if context.viewState.isModallyPresented == true, context.viewState.recoveryKey == nil { ToolbarItem(placement: .cancellationAction) { Button(L10n.actionCancel) { context.send(viewAction: .cancel) @@ -235,6 +232,8 @@ struct SecureBackupRecoveryKeyScreen_Previews: PreviewProvider, TestablePreview let backupController = SecureBackupControllerMock() backupController.underlyingRecoveryState = CurrentValueSubject(recoveryState).asCurrentValuePublisher() - return SecureBackupRecoveryKeyScreenViewModel(secureBackupController: backupController, userIndicatorController: UserIndicatorControllerMock()) + return SecureBackupRecoveryKeyScreenViewModel(secureBackupController: backupController, + userIndicatorController: UserIndicatorControllerMock(), + isModallyPresented: true) } } diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift index 693d972e9..2bfe65c09 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift @@ -46,7 +46,8 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol { let navigationStackCoordinator = NavigationStackCoordinator() let recoveryKeyCoordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: .init(secureBackupController: parameters.secureBackupController, - userIndicatorController: parameters.userIndicatorController)) + userIndicatorController: parameters.userIndicatorController, + isModallyPresented: true)) recoveryKeyCoordinator.actions.sink { [weak self] action in guard let self else { return } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift index cc3d8afd5..5a74d2fbd 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift @@ -33,7 +33,6 @@ enum SettingsScreenCoordinatorAction { case bugReport case about case blockedUsers - case sessionVerification case accountSessions case notifications case advancedSettings @@ -77,8 +76,6 @@ final class SettingsScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.about) case .blockedUsers: actionsSubject.send(.blockedUsers) - case .sessionVerification: - actionsSubject.send(.sessionVerification) case .secureBackup: actionsSubject.send(.secureBackup) case .accountSessionsList: diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift index 65535cb46..1d0d51e4e 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift @@ -26,7 +26,6 @@ enum SettingsScreenViewModelAction { case reportBug case about case blockedUsers - case sessionVerification case secureBackup case accountSessionsList case notifications @@ -37,7 +36,6 @@ enum SettingsScreenViewModelAction { enum SettingsScreenSecuritySectionMode { case none - case sessionVerification case secureBackup } @@ -65,7 +63,6 @@ enum SettingsScreenViewAction { case reportBug case about case blockedUsers - case sessionVerification case secureBackup case accountSessionsList case notifications diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index 8ea8d04aa..ebf0ffded 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -50,9 +50,6 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo guard let self else { return } switch (securityState.verificationState, securityState.recoveryState) { - case (.unverified, _): - state.showSecuritySectionBadge = true - state.securitySectionMode = .sessionVerification case (.verified, .disabled): state.showSecuritySectionBadge = true state.securitySectionMode = .secureBackup @@ -107,8 +104,6 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo actionsSubject.send(.blockedUsers) case .logout: actionsSubject.send(.logout) - case .sessionVerification: - actionsSubject.send(.sessionVerification) case .secureBackup: actionsSubject.send(.secureBackup) case .notifications: diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index 487083dce..6fa81c9aa 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -81,11 +81,6 @@ struct SettingsScreen: View { private var accountSecuritySection: some View { Section { switch context.viewState.securitySectionMode { - case .sessionVerification: - ListRow(label: .default(title: L10n.actionCompleteVerification, - icon: \.checkCircle), - details: context.viewState.showSecuritySectionBadge ? .icon(securitySectionBadge) : nil, - kind: .button { context.send(viewAction: .sessionVerification) }) case .secureBackup: ListRow(label: .default(title: L10n.commonChatBackup, icon: \.key), diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift index 785a52e46..8ebfbb9e7 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift @@ -190,7 +190,7 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { // MARK: Loading indicator - private static let loadingIndicatorIdentifier = "StartChatCoordinatorLoading" + private static let loadingIndicatorIdentifier = "\(StartChatScreenCoordinator.self)-Loading" private func showLoadingIndicator() { parameters.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift index 7cc95ca42..ead0e1163 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift @@ -145,7 +145,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie // MARK: Loading indicator - private static let loadingIndicatorIdentifier = "StartChatLoading" + private static let loadingIndicatorIdentifier = "\(StartChatScreenViewModel.self)-Loading" private func showLoadingIndicator() { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, diff --git a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift index d277c7063..fa7a7a5ed 100644 --- a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift +++ b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift @@ -64,15 +64,8 @@ final class NotificationManager: NSObject, NotificationManagerProtocol { self?.enableNotifications(newValue) } .store(in: &cancellables) - - // Request authorization uppon UIApplication.didBecomeActiveNotification notification - NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification) - .sink { [weak self] _ in - self?.requestAuthorization() - } - .store(in: &cancellables) } - + func requestAuthorization() { guard appSettings.enableNotifications, !userSession.isNil else { return } Task { diff --git a/ElementX/Sources/Services/Notification/Manager/NotificationManagerProtocol.swift b/ElementX/Sources/Services/Notification/Manager/NotificationManagerProtocol.swift index fdbb4b01f..7701c3d70 100644 --- a/ElementX/Sources/Services/Notification/Manager/NotificationManagerProtocol.swift +++ b/ElementX/Sources/Services/Notification/Manager/NotificationManagerProtocol.swift @@ -38,5 +38,6 @@ protocol NotificationManagerProtocol: AnyObject { func registrationFailed(with error: Error) func showLocalNotification(with title: String, subtitle: String?) async func setUserSession(_ userSession: UserSessionProtocol?) + func requestAuthorization() } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift index 6b05cbfee..b95c0f087 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift @@ -18,8 +18,7 @@ import Foundation struct MockRoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol { func buildRoomTimelineController(roomProxy: RoomProxyProtocol, - timelineItemFactory: RoomTimelineItemFactoryProtocol, - secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol { + timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol { let timelineController = MockRoomTimelineController() timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk return timelineController diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index d45576b46..e8ff0c612 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -24,7 +24,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol { private let timelineProvider: RoomTimelineProviderProtocol private let timelineItemFactory: RoomTimelineItemFactoryProtocol private let appSettings: AppSettings - private let secureBackupController: SecureBackupControllerProtocol private let serialDispatchQueue: DispatchQueue private var cancellables = Set() @@ -39,13 +38,11 @@ class RoomTimelineController: RoomTimelineControllerProtocol { init(roomProxy: RoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, - appSettings: AppSettings, - secureBackupController: SecureBackupControllerProtocol) { + appSettings: AppSettings) { self.roomProxy = roomProxy timelineProvider = roomProxy.timeline.timelineProvider self.timelineItemFactory = timelineItemFactory self.appSettings = appSettings - self.secureBackupController = secureBackupController serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility) timelineProvider @@ -253,7 +250,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol { var newTimelineItems = [RoomTimelineItemProtocol]() var canBackPaginate = !roomProxy.timeline.timelineStartReached var isBackPaginating = false - var lastEncryptedHistoryItemIndex: Int? let collapsibleChunks = timelineProvider.itemProxies.groupBy { isItemCollapsible($0) } @@ -264,10 +260,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol { let timelineItem = buildTimelineItem(for: itemProxy) - if timelineItem is EncryptedHistoryRoomTimelineItem { - canBackPaginate = false - } - return timelineItem } @@ -282,35 +274,25 @@ class RoomTimelineController: RoomTimelineControllerProtocol { continue } - if timelineItem is EncryptedHistoryRoomTimelineItem { - lastEncryptedHistoryItemIndex = newTimelineItems.endIndex - } - newTimelineItems.append(timelineItem) } else { newTimelineItems.append(CollapsibleTimelineItem(items: items)) } } - if let lastEncryptedHistoryItemIndex { - // Remove everything up to the last encrypted history item. - // It only contains encrypted messages, state changes and date separators. - newTimelineItems.removeFirst(lastEncryptedHistoryItemIndex) - } else { - // Otherwise check if we need to add anything to the top of the timeline. - switch timelineProvider.backPaginationState { - case .timelineStartReached: - if !roomProxy.isEncryptedOneToOneRoom { - let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name) - newTimelineItems.insert(timelineStart, at: 0) - } - canBackPaginate = false - case .paginating: - newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(), at: 0) - isBackPaginating = true - case .idle: - break + // Check if we need to add anything to the top of the timeline. + switch timelineProvider.backPaginationState { + case .timelineStartReached: + if !roomProxy.isEncryptedOneToOneRoom { + let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name) + newTimelineItems.insert(timelineStart, at: 0) } + canBackPaginate = false + case .paginating: + newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(), at: 0) + isBackPaginating = true + case .idle: + break } DispatchQueue.main.sync { @@ -326,17 +308,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { switch itemProxy { case .event(let eventTimelineItem): let timelineItem = timelineItemFactory.buildTimelineItem(for: eventTimelineItem, isDM: roomProxy.isEncryptedOneToOneRoom) - - // When backup is enabled just show the timeline items, they will most likely - // resolve eventually. If we don't know the backup state then we assume the session is not verified yet, - // otherwise we just treat it as fully disabled. - if secureBackupController.keyBackupState.value != .enabled { - if timelineItem is EncryptedRoomTimelineItem, isItemInEncryptionHistory(eventTimelineItem) { - return EncryptedHistoryRoomTimelineItem(id: eventTimelineItem.id, - isSessionVerified: secureBackupController.keyBackupState.value != .unknown) - } - } - + if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol { // Avoid fetching this over and over again as it changes states if it keeps failing to load // Errors will be handled again on appearance @@ -358,14 +330,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return nil } } - - /// Whether or not a specific item is part of the room's history that can't be decrypted due - /// to the lack of key-backup. This is handled differently so we only show a single item. - private func isItemInEncryptionHistory(_ itemProxy: EventTimelineItemProxy) -> Bool { - guard roomProxy.isEncrypted, let lastLoginDate = appSettings.lastLoginDate else { return false } - return itemProxy.timestamp < lastLoginDate - } - + private func isItemCollapsible(_ item: TimelineItemProxy) -> Bool { if !appSettings.shouldCollapseRoomStateEvents { return false diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift index 87aa4e848..199eeaaa7 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift @@ -18,11 +18,9 @@ import Foundation struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol { func buildRoomTimelineController(roomProxy: RoomProxyProtocol, - timelineItemFactory: RoomTimelineItemFactoryProtocol, - secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol { + timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol { RoomTimelineController(roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, - appSettings: ServiceLocator.shared.settings, - secureBackupController: secureBackupController) + appSettings: ServiceLocator.shared.settings) } } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift index 60fe5be1c..7e8db09c1 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift @@ -19,6 +19,5 @@ import Foundation @MainActor protocol RoomTimelineControllerFactoryProtocol { func buildRoomTimelineController(roomProxy: RoomProxyProtocol, - timelineItemFactory: RoomTimelineItemFactoryProtocol, - secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol + timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift index 0d999ca3a..98b0391da 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift @@ -64,8 +64,6 @@ struct RoomTimelineItemView: View { UnsupportedRoomTimelineView(timelineItem: item) case .timelineStart(let item): TimelineStartRoomTimelineView(timelineItem: item) - case .encryptedHistory(let item): - EncryptedHistoryRoomTimelineView(timelineItem: item) case .state(let item): StateRoomTimelineView(timelineItem: item) case .group(let item): diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewState.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewState.swift index c9a564327..e013043b6 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewState.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewState.swift @@ -60,7 +60,6 @@ enum RoomTimelineItemType: Equatable { case sticker(StickerRoomTimelineItem) case unsupported(UnsupportedRoomTimelineItem) case timelineStart(TimelineStartRoomTimelineItem) - case encryptedHistory(EncryptedHistoryRoomTimelineItem) case state(StateRoomTimelineItem) case group(CollapsibleTimelineItem) case location(LocationRoomTimelineItem) @@ -100,8 +99,6 @@ enum RoomTimelineItemType: Equatable { self = .unsupported(item) case let item as TimelineStartRoomTimelineItem: self = .timelineStart(item) - case let item as EncryptedHistoryRoomTimelineItem: - self = .encryptedHistory(item) case let item as StateRoomTimelineItem: self = .state(item) case let item as CollapsibleTimelineItem: @@ -136,7 +133,6 @@ enum RoomTimelineItemType: Equatable { .sticker(let item as RoomTimelineItemProtocol), .unsupported(let item as RoomTimelineItemProtocol), .timelineStart(let item as RoomTimelineItemProtocol), - .encryptedHistory(let item as RoomTimelineItemProtocol), .state(let item as RoomTimelineItemProtocol), .group(let item as RoomTimelineItemProtocol), .location(let item as RoomTimelineItemProtocol), diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index a20632214..2aa3afcbd 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -42,7 +42,7 @@ class UITestsAppCoordinator: AppCoordinatorProtocol, WindowManagerDelegate { ServiceLocator.shared.register(userIndicatorController: UserIndicatorController()) AppSettings.configureWithSuiteName("io.element.elementx.uitests") - AppSettings.reset() + AppSettings.resetAllSettings() ServiceLocator.shared.register(appSettings: AppSettings()) ServiceLocator.shared.register(bugReportService: BugReportServiceMock()) ServiceLocator.shared.register(analytics: AnalyticsService(client: AnalyticsClientMock(), @@ -149,7 +149,6 @@ class MockScreen: Identifiable { return navigationStackCoordinator case .authenticationFlow: let flowCoordinator = AuthenticationFlowCoordinator(authenticationService: MockAuthenticationServiceProxy(), - appLockService: AppLockServiceMock(), bugReportService: BugReportServiceMock(), navigationRootCoordinator: navigationRootCoordinator, appSettings: ServiceLocator.shared.settings, @@ -567,8 +566,7 @@ class MockScreen: Identifiable { return navigationStackCoordinator case .sessionVerification: var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(5)) - let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy, - recoveryState: .unknown) + let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy) return SessionVerificationScreenCoordinator(parameters: parameters) case .userSessionScreen, .userSessionScreenReply, .userSessionScreenRTE: let appSettings: AppSettings = ServiceLocator.shared.settings @@ -586,7 +584,9 @@ class MockScreen: Identifiable { bugReportService: BugReportServiceMock(), roomTimelineControllerFactory: MockRoomTimelineControllerFactory(), appSettings: appSettings, - analytics: ServiceLocator.shared.analytics) + analytics: ServiceLocator.shared.analytics, + notificationManager: NotificationManagerMock(), + isNewLogin: false) flowCoordinator.start() diff --git a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift index 66df82082..d8ee1972a 100644 --- a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift +++ b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift @@ -24,7 +24,7 @@ class UnitTestsAppCoordinator: AppCoordinatorProtocol { ServiceLocator.shared.register(userIndicatorController: UserIndicatorControllerMock.default) AppSettings.configureWithSuiteName("io.element.elementx.unittests") - AppSettings.reset() + AppSettings.resetAllSettings() ServiceLocator.shared.register(appSettings: AppSettings()) ServiceLocator.shared.register(bugReportService: BugReportServiceMock()) ServiceLocator.shared.register(analytics: AnalyticsService(client: AnalyticsClientMock(), diff --git a/IntegrationTests/Sources/Application.swift b/IntegrationTests/Sources/Application.swift index e006bbc8e..05a8aa9a5 100644 --- a/IntegrationTests/Sources/Application.swift +++ b/IntegrationTests/Sources/Application.swift @@ -19,7 +19,14 @@ import XCTest enum Application { @discardableResult static func launch() -> XCUIApplication { let app = XCUIApplication() + + var launchEnvironment = [ + "IS_RUNNING_INTEGRATION_TESTS": "1" + ] + + app.launchEnvironment = launchEnvironment app.launch() + return app } } diff --git a/IntegrationTests/Sources/Common.swift b/IntegrationTests/Sources/Common.swift index d0a02000e..34ec6c6a0 100644 --- a/IntegrationTests/Sources/Common.swift +++ b/IntegrationTests/Sources/Common.swift @@ -65,33 +65,13 @@ extension XCUIApplication { // Wait for login to finish currentTestCase.expectation(for: doesNotExistPredicate, evaluatedWith: usernameTextField) currentTestCase.waitForExpectations(timeout: 300.0) - - // Handle analytics prompt screen - if staticTexts[A11yIdentifiers.analyticsPromptScreen.title].waitForExistence(timeout: 10.0) { - // Wait for login and then handle save password sheet - let savePasswordButton = buttons["Save Password"] - if savePasswordButton.waitForExistence(timeout: 10.0) { - savePasswordButton.tap() - } - - let enableButton = buttons[A11yIdentifiers.analyticsPromptScreen.enable] - XCTAssertTrue(enableButton.waitForExistence(timeout: 10.0)) - enableButton.tap() - } - + // This might come in a different order, wait for both. let savePasswordButton = buttons["Save Password"] if savePasswordButton.waitForExistence(timeout: 10.0) { savePasswordButton.tap() } - // Handle the notifications permission alert https://stackoverflow.com/a/58171074/730924 - let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") - let notificationAlertDeclineButton = springboard.buttons.element(boundBy: 0) - if notificationAlertDeclineButton.waitForExistence(timeout: 10.0) { - notificationAlertDeclineButton.tap() - } - // Migration screen may be shown as an overlay. // if that pops up soon enough, we just let that happen and wait let message = staticTexts[A11yIdentifiers.migrationScreen.message] diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-en-GB.1.png index 0069e930e..2c2fac715 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70fcd44d149c0c7633fe2bee0ba85f3ae81d1fedd801a66239f0d1307909a430 -size 173623 +oid sha256:0a46cd7cf5e0e13a6d7c60300a9fcb6311c20bb2bae22778a2783a2f0afc7c0e +size 415851 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-pseudo.1.png index 5dcf51263..6db3ba7e1 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5eb23616fd2661ac5a2990e0dfef92c504da2726db2138222b88fba79fcd7500 -size 267411 +oid sha256:f83e16e68a399d2e9db7c69e3fd6fe83a42ebe497b23cb4dde5b6d230d373c4c +size 508866 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-en-GB.1.png index 2bfbab755..e6f23b962 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9be44c03d825f3650984e0cfd0540915913f896951acd0d39991f9843371c111 -size 122394 +oid sha256:7815860f7b70f4aca237db12ec782a0b39276e5b3e5144cb1535254992a25c57 +size 248416 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-pseudo.1.png index ec952675e..f58a7ef25 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28cffcec20c859af14d5e018c63da99c3014375d56ffd30751c15846f823f031 -size 200260 +oid sha256:ef5bc386c26e9f4777531b8e439de9cad04a98d02e25d4f7298192589720b0b9 +size 325644 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPad-en-GB.1.png deleted file mode 100644 index ab2e817ac..000000000 --- a/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPad-en-GB.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5832efbb9c9ee554cbcb18a9d36931e0d255385791994ec328fb489ab571c731 -size 118356 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPad-pseudo.1.png deleted file mode 100644 index 59c93729f..000000000 --- a/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPad-pseudo.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dfe734442f32895cf014454a9819b0c9dffcb47866876294156cb209b3695278 -size 163863 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPhone-15-en-GB.1.png deleted file mode 100644 index e3f1c4fe4..000000000 --- a/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPhone-15-en-GB.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47d083e6284305c4c8c182415a15b28ed58c0e4435420557c5628bb51fb79e2c -size 70572 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPhone-15-pseudo.1.png deleted file mode 100644 index 4d5c9be6f..000000000 --- a/PreviewTests/__Snapshots__/PreviewTests/test_homeScreenSessionVerificationBanner-iPhone-15-pseudo.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f87532f0e14e153708a24b12d5c1f232eb537fa9317d0906562c03e3e7ad8617 -size 105302 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-en-GB.1.png new file mode 100644 index 000000000..76e8ab464 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47d502c8c6fbec6e38497bf9aabe3803f9006145d5c7e9c0997f892405826bc1 +size 119605 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-pseudo.1.png new file mode 100644 index 000000000..9e713c4aa --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86e9a45f6ab26a072edfba0258e275d2f234765ecf0a69f1301aba6108090d07 +size 153905 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..752e6039c --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15e443ca166ca691ac1f7ba73732e564c5ea3e18297f290337d94fc55da1a821 +size 71486 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..934c7ff3e --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f44459b131c9d4a9c88547b9b58c23814919fe2311ffac7694cf19e5f584e817 +size 106764 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPad-en-GB.1.png new file mode 100644 index 000000000..897fed7e7 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8dfc9897f5cef15fa16a1e51ec869b6dfd3c05a1376300ffed7440ff4b490d0c +size 120872 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPad-pseudo.1.png new file mode 100644 index 000000000..34d63ed50 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dd9ce4a46cff45390876e7dfdbaeecce78c69a40a403f8563544e05e3efc379 +size 162211 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..8bb83fea5 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a9cd6d7a9970fa0a77aff7201990172dfdfb9c25c48b4197ab5c229f01bffce +size 73653 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..09ff97a03 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmedScreen-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be25dc7183d5821ffab59e57d022f69bc014aa7b51111ca57cba636f52934d18 +size 114490 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPad-en-GB.1.png new file mode 100644 index 000000000..b0121994b --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab736cdeff320ccf70d67b34009536b723ce9abe7348d8f885caf74a17967229 +size 389835 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPad-pseudo.1.png new file mode 100644 index 000000000..43f8bc439 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b591ed050b9a913a500c0ef0bb935bd7940dfe631a3486810ab18b5ebbe947c +size 441047 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..9a1fc7771 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10a1bb28971bb64a6679b6a4cfd08c060f5305e40032768db88ffd24a97dab8c +size 214792 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..d3bd39e0a --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_notificationPermissionsScreen-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95219b6d0cff9700acf62799558ad527ed405cafb4565fbe8c48e8b3210d7b4c +size 254130 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-en-GB.Set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-en-GB.Set-up.png index 4f0918d3d..97d2d5223 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-en-GB.Set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-en-GB.Set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a961330081c52fb6d358ee458351c5a9e9b482e2dc129fc30bd4365877ff6bd5 -size 188454 +oid sha256:3d8c27b325869c82555047403415f58e28872e76e5f045cd064b2ab3ba243fc1 +size 196801 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-pseudo.Set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-pseudo.Set-up.png index 10d69526f..cb3039bf9 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-pseudo.Set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPad-pseudo.Set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d7f02dcad9dd8ded74b8d3ae51b010085ea800143426d32eaa41084ba142e7f -size 287728 +oid sha256:dc127a68581e3163e6f2349bd7e2ffc201ce2fa8fd066187b483ddb33fd67fa0 +size 304422 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPhone-15-en-GB.Set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPhone-15-en-GB.Set-up.png index 800ebb285..9fc6e3716 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPhone-15-en-GB.Set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPhone-15-en-GB.Set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92e84ea5a58fb3b0337e5221c91aae1c640c1d79ac3f5d1002fddb301cfe35e0 -size 146863 +oid sha256:b8363d9bb0f0497005e0d88487aea70523c08fe88d7ac97185dfcfd5e71784d4 +size 147319 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPhone-15-pseudo.Set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPhone-15-pseudo.Set-up.png index ee6bfd7dc..ad10f0d71 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPhone-15-pseudo.Set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupKeyBackupScreen-iPhone-15-pseudo.Set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb6d2272487d56ad6c91d206ebcae25734d1b2ddf41106cbe29ceb9a8c92c2e4 -size 228920 +oid sha256:49ca04fc3f8ac3eb8bd54b360004f8fecce4aeb012436b090403328b69d23a99 +size 228907 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPad-en-GB.1.png index 649037ec2..4788ffbbb 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPad-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0e94ed0775d91f4b05caebdac22c73f120a6861b621269108a35c30a8f478a5 -size 143570 +oid sha256:f6d0962c09da93a8abcc499017c24c28555622e3e02d978043b3c7f3874729f2 +size 156994 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPad-pseudo.1.png index 2cefcd6c0..4214cdadf 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPad-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5805c22d5fd0e778eee4ab6f6e2065c4500abe121edb94c2a4a2e4d0f9eab4f8 -size 202973 +oid sha256:df6d9523e1659465f080f56e76db707fbe027803be5f83e0e9b8dc064d1251a8 +size 217178 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPhone-15-en-GB.1.png index 61533439e..cd3290605 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPhone-15-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPhone-15-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3b7ea1241f90e40c8677c0ee085b6c9a4e4df35b6492e61b7aaa7f20fa673a2 -size 94098 +oid sha256:16a97c200a8e66deb943df7cb8bde5a1f84776f680fd1a89cbc80d474b87eba7 +size 101247 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPhone-15-pseudo.1.png index 0deac2487..479041026 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPhone-15-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupLogoutConfirmationScreen-iPhone-15-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1979f42f8b6e8c3a40d24af8731d5d843bab9452c007ea477dee7a299ee28273 -size 146888 +oid sha256:76f33c85188d8c10bc304dd4bd5d7d92652b184204a1720650189a4d6da8663b +size 159534 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Incomplete.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Incomplete.png index 44fc2709e..2ac0f706a 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Incomplete.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Incomplete.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30240be156fa9702ab5a06af238f883c3729c4d67787c512c3afd8e4c5380201 -size 137295 +oid sha256:baf9353b8c7a809f2156328608d6a104f1e0ca70132ab635932ad840344c55f4 +size 137132 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Not-set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Not-set-up.png index d932844a1..f332c9442 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Not-set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Not-set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ceb94c28825aee0aba729eb8c236488a5b8aa44fdb8f7721df6f4a4682e89e6 -size 172886 +oid sha256:e7c5c210baa3a397ea02c6a1af578b71d74a9ec0b16f0fb3bdfba95d5ff07232 +size 179233 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Set-up.png index 7f6d022aa..60bb1ea1a 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-en-GB.Set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72e2785a7cbd3449ad18851156c5ad20c4cf39f23e88f6cc2fd9f3664f52da5f -size 171608 +oid sha256:28e9bbf6e09a25bb02a9a9f678520d64b75544923628ee730c3565402daa5ee5 +size 182095 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Incomplete.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Incomplete.png index 1dacba460..cccb8beff 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Incomplete.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Incomplete.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1b51a5fab01b184ade32cbc30e7dcd0cc7b9c41d8d9de97ae010890f0735adb -size 182447 +oid sha256:4bdfa8f2fdb918150acf2b4d923ed3a2037e1b4cbbf7cb42f46353f4dadcd9f8 +size 195582 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Not-set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Not-set-up.png index e5a57a4d1..f01b76933 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Not-set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Not-set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac04fbf081d504bbc9f048bdf69bfa1aaa274182a1398dbc4f4453cf69d37ab5 -size 259583 +oid sha256:7aa089fb7520df5dcdb62e350db2342b0510f6ef62a31864f376b17cfe3c295b +size 278457 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Set-up.png index e9d8e956f..575e3c37b 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPad-pseudo.Set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:074bf361c6a7d4df4768ccdeb12bf2a13608fd307b6b2ce2539d9e69d3ab6cda -size 251075 +oid sha256:3adfc60a26f525cecdd61437ddc2bddac5b21cbe265bd0ce1ecfd406c2750bed +size 267003 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Incomplete.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Incomplete.png index 11096840b..7bfa7f541 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Incomplete.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Incomplete.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac47db578faa7c4744ad058955324090b20ce8e531c4f5d0bcdd4533f6830483 -size 89728 +oid sha256:b22c4508725a4db10d1fff18cffca76d4690dfe84e48237a8f83936b54718576 +size 90849 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Not-set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Not-set-up.png index 1d10f35de..d7084d53c 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Not-set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Not-set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fea447cd269455d6324e27ee2c24e702fa6f7d55929ab0d4367b31c18d9fa8e -size 124048 +oid sha256:c2a76ee26b4f5d690f67aabd7f86494621823eacab9e5400c203ef5b85abab11 +size 129082 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Set-up.png index 1318c28ad..c0e6eaf50 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-en-GB.Set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3515b93222d228e5e2dcaec8e1de42cace4a520354423418d3c5b1bb1f212efe -size 126061 +oid sha256:94f45a1eef76250e677b87a35231bfe17b2ee3e27495c907b8e3b0ebe876898c +size 129889 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Incomplete.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Incomplete.png index 77c35dcb5..a28e57d95 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Incomplete.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Incomplete.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7a93826727cdd4f3cbfb2b04079b23f07efe2d43bf30eaaebd9124a6d14df2f -size 131666 +oid sha256:b21f513f45a15eef2b500c534ec7c60daf6b658cef2fef44b73b58cca8575c66 +size 138362 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Not-set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Not-set-up.png index cef354b32..6dc9435a7 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Not-set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Not-set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af863478b0d63e57d2c8e876ed7911bf98c65830748cea0fb582c3d29e285a8f -size 212546 +oid sha256:201ba4003d78745c4b6387726d640604d80e9f05cf9e9f44e4e26c0583d18394 +size 224523 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Set-up.png b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Set-up.png index 1a27c71fd..93b8d2194 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Set-up.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_secureBackupRecoveryKeyScreen-iPhone-15-pseudo.Set-up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8dba9aac09c608abf637c9527d2ab6f026f4443fe4671de7376582b089493a78 -size 202804 +oid sha256:1d12dc8245858d615eb22ddb811d9663a3845c0943c6da1a024cd6b7afe3b351 +size 218259 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Cancelled.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Cancelled.png index 258635e05..abc8d7bfa 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Cancelled.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Cancelled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f11bc47b5fa10be69909319681ead2fd1f749c7eff763a788ec38d2cfe15b163 -size 122535 +oid sha256:7d5c3d555619bdd748dd000259f2164e288d8f7e0a751088d754435ed7fb4029 +size 124629 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Initial.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Initial.png index c812d23ee..728d0eb79 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Initial.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Initial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8aa5677fe309a3ef02fbf62821c559d5529d0ba85faf492e041198f18c74d3a -size 131005 +oid sha256:c2d80c9a388f9ad1d6bbaa2ed83f2f02dc81b8fbf01d8ff96f7bd673500c094d +size 122493 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Request-Accepted.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Request-Accepted.png index e36bbab72..0c41faca4 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Request-Accepted.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Request-Accepted.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fad40f3a6a65e5fb257d42c0977c51834e52f7d9db00a0cf76ee73a0a5fc2db9 -size 118552 +oid sha256:9da8908c5b57fb81d15e7460853e89ae37910020f33b4641a0b237feaaf1cf24 +size 112890 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Requesting-Verification.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Requesting-Verification.png index be600fdce..6826d520f 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Requesting-Verification.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Requesting-Verification.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf6062e17075c673bc7e4ac0f71261f0dfb6f5a3fab7909dd8c56b0e347ba60b -size 119456 +oid sha256:955beb56556aa67500cc77c8b9da243da18729663e1d033323962e3e7e6b1ca5 +size 124265 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Showing-Challenge.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Showing-Challenge.png index a851fbbd5..8035b4cdc 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Showing-Challenge.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Showing-Challenge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ece83e5ae298e2ef469632bdde8a165993568ddd629249b9d37d6fa46d21f166 -size 203235 +oid sha256:08a81a2769a8e8f697525080a845e283a71edaeaa7d3462b4a865f8ce3ecd443 +size 202818 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Verified.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Verified.png index 2c4c8c88a..ae4792d60 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Verified.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-en-GB.Verified.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7578c0de358bac94f3bcb8be0d1a1d17205a193be3abfde52bcb8fc1985c76d -size 122484 +oid sha256:13a68b96593f09de7300f9031f17dd0c35684531cb318c734e94db959748f94d +size 125490 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Cancelled.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Cancelled.png index 6d37377a4..e7f007d08 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Cancelled.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Cancelled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eecc61d3446466cc3f3da6f4a23f7892762ec1dafab5f14246609a1af8a78195 -size 164759 +oid sha256:af5e0d91c5a10ce75e42dba7ffc73c91979c3bbfcb35efe12e0931fb6e30b459 +size 167169 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Initial.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Initial.png index a85cb636c..3ace83cbc 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Initial.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Initial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4698453b82745f07f1b83eea133d3d540c1fc9891ddd5bcf301f34bb9e13efd5 -size 181226 +oid sha256:db4372d64cc643e0e07226bd794a478f4fd3329ff2c0e1beb59d9762e5ed7094 +size 162017 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Request-Accepted.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Request-Accepted.png index ccc8760a8..c749f9b81 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Request-Accepted.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Request-Accepted.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd0d3d827c5c8771341cd71f05a62af980113d60e7e069731b2c813971a6576e -size 148465 +oid sha256:326d124d879ddd3460209f4b104bc4c6ea6d5a739029086bcd4d218bcd29a548 +size 151351 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Requesting-Verification.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Requesting-Verification.png index 33742e0c9..ed0c6caee 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Requesting-Verification.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Requesting-Verification.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:858f8dd1d64c4e3ab9696d111c786dbc8ebd292a66c0a34b9d7d1762d9e0c3ad -size 161853 +oid sha256:42587aaca36304daf16e419bbcf3ae3b02db96638262f38d49dbe40b2f2a70ee +size 165908 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Showing-Challenge.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Showing-Challenge.png index 3ecfb5194..4685a1790 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Showing-Challenge.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Showing-Challenge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb6ae2fd7378b5858fd6907cecc46b6136416ca6c979a61e13b7b2720712060a -size 254477 +oid sha256:108f111d9c80da099d29eb9693f15b3ce5fc9c8f81365ce02c36ed5dbf8d24e8 +size 256712 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Verified.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Verified.png index 443304b94..e82200e7c 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Verified.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPad-pseudo.Verified.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c13077c41cb571d2d55fe3b8c47e3dcef99611d1d5d2154a37e96b96a13f4d90 -size 171286 +oid sha256:8872853b5b71cca86d37fd800391631464f791cc6095d3a0a50839b5fffbf351 +size 173239 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Cancelled.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Cancelled.png index 708970417..56aef81bb 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Cancelled.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Cancelled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2a96129826e8f24e5b4fa417cdc104909fb88be4fdb3d740761e50c0e31df7b -size 78576 +oid sha256:296d8b28d222c3ef9f6cf7725456221b36822e4eb8c117aa52981cda89e6a101 +size 75022 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Initial.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Initial.png index 3811f028f..1a8e3a1db 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Initial.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Initial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56b0318d28b8cedff62792e6be7e65871dd130e1f2a2b46efa11b8d643a4388c -size 86673 +oid sha256:48d31bd2915e6b7f34ca93855279c76d100b5ff30f8f37097262c3776058bf87 +size 73446 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Request-Accepted.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Request-Accepted.png index 12d6ef3ec..2a45dceca 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Request-Accepted.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Request-Accepted.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df6adb6614f1f916c223574798fe4e0599af323236f04e27a91303d64707087a -size 72320 +oid sha256:a3265aa36d9f0d2cc6751fc6d034c3ffbdd1ac79626e99da19c196fd18f43583 +size 68232 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Requesting-Verification.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Requesting-Verification.png index 31778d671..45e6895fb 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Requesting-Verification.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Requesting-Verification.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f53b5a56e45c00c47e6ec825e2c9261e6edfe1c7d87f5f55da669d388028a3f8 -size 78458 +oid sha256:b5581a2a8067d8e9e1583f550428cd41b7e3dfedfd43cab603b1f02c2b3994a3 +size 74678 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Showing-Challenge.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Showing-Challenge.png index a66c8cba6..e7b51c474 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Showing-Challenge.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Showing-Challenge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:071580d15a2f417df09ad9913a336d3a9719feb65c724708458942ac7ce41b4a -size 149999 +oid sha256:4a1fc2cd381c49b911e4cb4f133fc09f601668f58fd095cdbc964cafcb010114 +size 145509 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Verified.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Verified.png index 3b8d6d041..ae6f94631 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Verified.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-en-GB.Verified.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a98db6b8dc611df5dd12a19b9eafe1268eea72701d8e3c1c6b08c8602d806897 -size 77593 +oid sha256:60a25f5124021ee967d989b115f37bb56b800908d8ab0e603f982c4214d18ba0 +size 73933 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Cancelled.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Cancelled.png index fed715c63..6e010b192 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Cancelled.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Cancelled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62b7fe25164a6d84df7aab70c63cb907ea7f12e3d9f4befc68820ab7f815ddad -size 115441 +oid sha256:86df5d48b1af9d36e0aa210e4d569d10d24bb170c678e65b1cc6e84f5811f28c +size 108486 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Initial.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Initial.png index 72b3c38ef..8f3416993 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Initial.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Initial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fddf070b4223906a9af6c30ab4f72a0fccf999eeb97e3828b4b1343afc376653 -size 133556 +oid sha256:c5d923e3408694e036bc9cf0884d8d992be57077c1989042beac3b39b13516dd +size 111808 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Request-Accepted.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Request-Accepted.png index 2c9ff02a7..073dd2086 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Request-Accepted.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Request-Accepted.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd2d09544c3f2aed9f5a931d0e4553247259f07ded32b8f67d6f93063d771cc6 -size 107939 +oid sha256:acde40c352609c2d3603041c1bbfb230781262d7c62e882492ca823b5dcbc5dd +size 100388 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Requesting-Verification.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Requesting-Verification.png index 8373064ae..f392944f3 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Requesting-Verification.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Requesting-Verification.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c0e3bff463860a99a7d8b5d55705fe67ccf8fe2383107e6dad3100309d98e44 -size 118912 +oid sha256:d83f9afa2c3a44d26d3ed077e932f28282b9c1e009e3430d422ee0f3af06aba8 +size 110056 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Showing-Challenge.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Showing-Challenge.png index 162c6daa8..e26fc379b 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Showing-Challenge.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Showing-Challenge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9371f5c4c9648736c08507b2eaec2486a88372ecbd067e2242eff58e7047104c -size 201928 +oid sha256:cafcb832b6ecc7bfc15fb5c4ed1f802a68231360e38fae6a56cfff1013a75a19 +size 195326 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Verified.png b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Verified.png index d538609d8..0e80b4fb0 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Verified.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_sessionVerification-iPhone-15-pseudo.Verified.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9baade6f1527a66442c8947b901d2e4a5c0946e132310fd02c9fd8b1cedd36bb -size 124007 +oid sha256:791fbd6b6784eb0fedbbcf3bff1b3c9a77e6d86ffb58939192a5e4d910505477 +size 116469 diff --git a/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift b/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift index 283f4deac..bb2fa19f6 100644 --- a/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift @@ -25,11 +25,11 @@ class AnalyticsSettingsScreenViewModelTests: XCTestCase { private var context: AnalyticsSettingsScreenViewModelType.Context! override func tearDown() { - AppSettings.reset() + AppSettings.resetAllSettings() } @MainActor override func setUpWithError() throws { - AppSettings.reset() + AppSettings.resetAllSettings() appSettings = AppSettings() let analyticsClient = AnalyticsClientMock() analyticsClient.isRunning = false diff --git a/UnitTests/Sources/AnalyticsTests.swift b/UnitTests/Sources/AnalyticsTests.swift index 525808560..9653c9752 100644 --- a/UnitTests/Sources/AnalyticsTests.swift +++ b/UnitTests/Sources/AnalyticsTests.swift @@ -24,7 +24,7 @@ class AnalyticsTests: XCTestCase { private var bugReportService: BugReportServiceMock! override func setUp() { - AppSettings.reset() + AppSettings.resetAllSettings() appSettings = AppSettings() bugReportService = BugReportServiceMock() @@ -38,7 +38,7 @@ class AnalyticsTests: XCTestCase { } override func tearDown() { - AppSettings.reset() + AppSettings.resetAllSettings() } func testAnalyticsPromptNewUser() { diff --git a/UnitTests/Sources/AppLock/AppLockScreenViewModelTests.swift b/UnitTests/Sources/AppLock/AppLockScreenViewModelTests.swift index 207655ff8..d36cc0fb7 100644 --- a/UnitTests/Sources/AppLock/AppLockScreenViewModelTests.swift +++ b/UnitTests/Sources/AppLock/AppLockScreenViewModelTests.swift @@ -28,7 +28,7 @@ class AppLockScreenViewModelTests: XCTestCase { var context: AppLockScreenViewModelType.Context { viewModel.context } override func setUp() { - AppSettings.reset() + AppSettings.resetAllSettings() appSettings = AppSettings() keychainController = KeychainControllerMock() appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings) @@ -36,7 +36,7 @@ class AppLockScreenViewModelTests: XCTestCase { } override func tearDown() { - AppSettings.reset() + AppSettings.resetAllSettings() } func testUnlock() async throws { diff --git a/UnitTests/Sources/AppLock/AppLockServiceTests.swift b/UnitTests/Sources/AppLock/AppLockServiceTests.swift index bfc953ed4..7048bdcac 100644 --- a/UnitTests/Sources/AppLock/AppLockServiceTests.swift +++ b/UnitTests/Sources/AppLock/AppLockServiceTests.swift @@ -25,7 +25,7 @@ class AppLockServiceTests: XCTestCase { var service: AppLockService! override func setUp() { - AppSettings.reset() + AppSettings.resetAllSettings() appSettings = AppSettings() keychainController = KeychainController(service: .tests, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) @@ -36,7 +36,7 @@ class AppLockServiceTests: XCTestCase { } override func tearDown() { - AppSettings.reset() + AppSettings.resetAllSettings() } // MARK: - PIN Code diff --git a/UnitTests/Sources/AppLock/AppLockSetupBiometricsScreenViewModelTests.swift b/UnitTests/Sources/AppLock/AppLockSetupBiometricsScreenViewModelTests.swift index 41bb24623..07fdc17d6 100644 --- a/UnitTests/Sources/AppLock/AppLockSetupBiometricsScreenViewModelTests.swift +++ b/UnitTests/Sources/AppLock/AppLockSetupBiometricsScreenViewModelTests.swift @@ -26,7 +26,7 @@ class AppLockSetupBiometricsScreenViewModelTests: XCTestCase { var context: AppLockSetupBiometricsScreenViewModelType.Context { viewModel.context } override func setUp() { - AppSettings.reset() + AppSettings.resetAllSettings() appLockService = AppLockServiceMock() appLockService.underlyingIsEnabled = true @@ -36,7 +36,7 @@ class AppLockSetupBiometricsScreenViewModelTests: XCTestCase { } override func tearDown() { - AppSettings.reset() + AppSettings.resetAllSettings() } func testAllow() async throws { diff --git a/UnitTests/Sources/AppLock/AppLockSetupPINScreenViewModelTests.swift b/UnitTests/Sources/AppLock/AppLockSetupPINScreenViewModelTests.swift index c0c7b2f04..1cc4043e8 100644 --- a/UnitTests/Sources/AppLock/AppLockSetupPINScreenViewModelTests.swift +++ b/UnitTests/Sources/AppLock/AppLockSetupPINScreenViewModelTests.swift @@ -27,13 +27,13 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase { var context: AppLockSetupPINScreenViewModelType.Context { viewModel.context } override func setUp() { - AppSettings.reset() + AppSettings.resetAllSettings() keychainController = KeychainControllerMock() appLockService = AppLockService(keychainController: keychainController, appSettings: AppSettings()) } override func tearDown() { - AppSettings.reset() + AppSettings.resetAllSettings() } func testCreatePIN() async throws { diff --git a/UnitTests/Sources/AppRouteURLParserTests.swift b/UnitTests/Sources/AppRouteURLParserTests.swift index 44319eeb5..a7a29412e 100644 --- a/UnitTests/Sources/AppRouteURLParserTests.swift +++ b/UnitTests/Sources/AppRouteURLParserTests.swift @@ -23,7 +23,7 @@ class AppRouteURLParserTests: XCTestCase { var appRouteURLParser: AppRouteURLParser! override func setUp() { - AppSettings.reset() + AppSettings.resetAllSettings() appSettings = AppSettings() appRouteURLParser = AppRouteURLParser(appSettings: appSettings) } diff --git a/UnitTests/Sources/ComposerToolbarViewModelTests.swift b/UnitTests/Sources/ComposerToolbarViewModelTests.swift index 6bc2b1938..ccdf536af 100644 --- a/UnitTests/Sources/ComposerToolbarViewModelTests.swift +++ b/UnitTests/Sources/ComposerToolbarViewModelTests.swift @@ -28,7 +28,7 @@ class ComposerToolbarViewModelTests: XCTestCase { private var completionSuggestionServiceMock: CompletionSuggestionServiceMock! override func setUp() { - AppSettings.reset() + AppSettings.resetAllSettings() appSettings = AppSettings() appSettings.richTextEditorEnabled = true ServiceLocator.shared.register(appSettings: appSettings) @@ -42,7 +42,7 @@ class ComposerToolbarViewModelTests: XCTestCase { } override func tearDown() { - AppSettings.reset() + AppSettings.resetAllSettings() } func testComposerFocus() { diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index b1737f664..46dbcc598 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -41,7 +41,7 @@ class HomeScreenViewModelTests: XCTestCase { } override func tearDown() { - AppSettings.reset() + AppSettings.resetAllSettings() } func testSelectRoom() async throws { diff --git a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift index 424feb881..701614ac7 100644 --- a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift +++ b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift @@ -37,7 +37,7 @@ final class NotificationManagerTests: XCTestCase { private var appSettings: AppSettings { ServiceLocator.shared.settings } override func setUp() { - AppSettings.reset() + AppSettings.resetAllSettings() notificationCenter = UserNotificationCenterMock() notificationCenter.requestAuthorizationOptionsReturnValue = true diff --git a/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift b/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift index 9000eb5be..bc3eeb708 100644 --- a/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift @@ -28,7 +28,7 @@ class NotificationSettingsScreenViewModelTests: XCTestCase { private var notificationSettingsProxy: NotificationSettingsProxyMock! @MainActor override func setUpWithError() throws { - AppSettings.reset() + AppSettings.resetAllSettings() userNotificationCenter = UserNotificationCenterMock() userNotificationCenter.authorizationStatusReturnValue = .authorized diff --git a/UnitTests/Sources/PermalinkBuilderTests.swift b/UnitTests/Sources/PermalinkBuilderTests.swift index 5e0264eaf..053334ba0 100644 --- a/UnitTests/Sources/PermalinkBuilderTests.swift +++ b/UnitTests/Sources/PermalinkBuilderTests.swift @@ -22,7 +22,7 @@ class PermalinkBuilderTests: XCTestCase { override func setUp() { AppSettings.configureWithSuiteName("io.element.elementx.unitests") - AppSettings.reset() + AppSettings.resetAllSettings() appSettings = AppSettings() } diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index 0b21fdac8..a7abfbbcc 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -43,7 +43,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { mentionBuilder: MentionBuilder()), appSettings: ServiceLocator.shared.settings) - AppSettings.reset() + AppSettings.resetAllSettings() } func testLeaveRoomTappedWhenPublic() async throws { diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index f95103d3a..c3dbaeecb 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -25,7 +25,7 @@ class RoomScreenViewModelTests: XCTestCase { var cancellables = Set() override func setUp() async throws { - AppSettings.reset() + AppSettings.resetAllSettings() cancellables.removeAll() userIndicatorControllerMock = UserIndicatorControllerMock.default } diff --git a/UnitTests/Sources/SessionVerificationViewModelTests.swift b/UnitTests/Sources/SessionVerificationViewModelTests.swift index c17e27b42..5ec7c4b21 100644 --- a/UnitTests/Sources/SessionVerificationViewModelTests.swift +++ b/UnitTests/Sources/SessionVerificationViewModelTests.swift @@ -27,8 +27,7 @@ class SessionVerificationViewModelTests: XCTestCase { override func setUpWithError() throws { sessionVerificationController = SessionVerificationControllerProxyMock.configureMock() - viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: sessionVerificationController, - recoveryState: .incomplete) + viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: sessionVerificationController) context = viewModel.context } @@ -47,7 +46,7 @@ class SessionVerificationViewModelTests: XCTestCase { context.send(viewAction: .requestVerification) - context.send(viewAction: .close) + viewModel.stop() XCTAssertEqual(context.viewState.verificationState, .cancelling) diff --git a/changelog.d/2592.change b/changelog.d/2592.change new file mode 100644 index 000000000..51f407504 --- /dev/null +++ b/changelog.d/2592.change @@ -0,0 +1 @@ +Move session verification to the onboarding flows, make it mandatory \ No newline at end of file diff --git a/changelog.d/2593.feature b/changelog.d/2593.feature new file mode 100644 index 000000000..029fe0a66 --- /dev/null +++ b/changelog.d/2593.feature @@ -0,0 +1 @@ +Introduce a new notification permissions screen as part of the onboarding flows \ No newline at end of file diff --git a/changelog.d/2594.change b/changelog.d/2594.change new file mode 100644 index 000000000..0e594823b --- /dev/null +++ b/changelog.d/2594.change @@ -0,0 +1 @@ +Remove the outdated welcome screen \ No newline at end of file diff --git a/changelog.d/2595.change b/changelog.d/2595.change new file mode 100644 index 000000000..4968492ce --- /dev/null +++ b/changelog.d/2595.change @@ -0,0 +1 @@ +Update onboarding flows to consolidate identity confirmation, analytics consent and notification permissions \ No newline at end of file diff --git a/project.yml b/project.yml index 66dcde14a..743cd8a97 100644 --- a/project.yml +++ b/project.yml @@ -29,7 +29,7 @@ settings: APP_NAME: ElementX APP_DISPLAY_NAME: Element X KEYCHAIN_ACCESS_GROUP_IDENTIFIER: $(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER) - MARKETING_VERSION: 1.5.13 + MARKETING_VERSION: 1.6.0 CURRENT_PROJECT_VERSION: 1 DEVELOPMENT_TEAM: 7J4U792NQT SUPPORTS_MACCATALYST: NO