Encryption Flow Coordinators. (#3471)

* Manage the secure backup screens with flow coordinators.

* Add UI tests for the EncryptionSettingsFlowCoordinator.

* Realise that the settings flow can't reset anymore and remove the sub-flow 🤦‍♂️

* Add UI tests for the EncryptionResetFlowCoordinator.
This commit is contained in:
Doug 2024-11-04 14:22:50 +00:00 committed by GitHub
parent 85e1de2c1c
commit 8e26718d0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 822 additions and 226 deletions

View File

@ -158,6 +158,7 @@
21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD558A898847C179E4B7A237 /* SecureBackupKeyBackupScreen.swift */; }; 21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD558A898847C179E4B7A237 /* SecureBackupKeyBackupScreen.swift */; };
22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; }; 22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; };
22C5483D01EEB290B8339817 /* HomeScreenInviteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FC33C3F6BF597E095CE9FA /* HomeScreenInviteCell.swift */; }; 22C5483D01EEB290B8339817 /* HomeScreenInviteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FC33C3F6BF597E095CE9FA /* HomeScreenInviteCell.swift */; };
230981086F0199F913434D6B /* EncryptionSettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF8E5D4C95974B96A18C80E /* EncryptionSettingsUITests.swift */; };
2335D1AB954C151FD8779F45 /* RoomPermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */; }; 2335D1AB954C151FD8779F45 /* RoomPermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */; };
23701DE32ACD6FD40AA992C3 /* MediaUploadingPreprocessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE203026B9AD3DB412439866 /* MediaUploadingPreprocessorTests.swift */; }; 23701DE32ACD6FD40AA992C3 /* MediaUploadingPreprocessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE203026B9AD3DB412439866 /* MediaUploadingPreprocessorTests.swift */; };
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */; }; 237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */; };
@ -205,6 +206,7 @@
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */; }; 2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */; };
2CA61BB208CD82EBDB58CD13 /* VideoRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */; }; 2CA61BB208CD82EBDB58CD13 /* VideoRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */; };
2CA6ABBC9A88EB89EA52FCCB /* ConfettiScene.scn in Resources */ = {isa = PBXBuildFile; fileRef = B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */; }; 2CA6ABBC9A88EB89EA52FCCB /* ConfettiScene.scn in Resources */ = {isa = PBXBuildFile; fileRef = B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */; };
2D0E3983288E2D35613AD681 /* SecureBackupControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */; };
2D2D8A53B35BE8D8A01449C6 /* PinnedEventsBannerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */; }; 2D2D8A53B35BE8D8A01449C6 /* PinnedEventsBannerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */; };
2DA27D78560D5F79B917E163 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44E35AA87F49503E7B3BF6E /* AudioConverter.swift */; }; 2DA27D78560D5F79B917E163 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44E35AA87F49503E7B3BF6E /* AudioConverter.swift */; };
2DD9D0FE7CB5CFC80D071451 /* AppLockScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */; }; 2DD9D0FE7CB5CFC80D071451 /* AppLockScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */; };
@ -353,6 +355,7 @@
4CE638FD837ED72CD98AD9A9 /* AppHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */; }; 4CE638FD837ED72CD98AD9A9 /* AppHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */; };
4D0F4385B7DDB68C66C78857 /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C258C9C815272911A5B132C3 /* FormattedBodyText.swift */; }; 4D0F4385B7DDB68C66C78857 /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C258C9C815272911A5B132C3 /* FormattedBodyText.swift */; };
4D23D41B8109E010304050F8 /* QRCodeLoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA551A98778CEE7366838CE2 /* QRCodeLoginScreenCoordinator.swift */; }; 4D23D41B8109E010304050F8 /* QRCodeLoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA551A98778CEE7366838CE2 /* QRCodeLoginScreenCoordinator.swift */; };
4D2B54233C7B2C04B4ABE55A /* EncryptionSettingsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */; };
4D4D236F0BBCDC4D2CBCCBB5 /* RoomChangePermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C729D95CB4588D4D9AAC3DFA /* RoomChangePermissionsScreenModels.swift */; }; 4D4D236F0BBCDC4D2CBCCBB5 /* RoomChangePermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C729D95CB4588D4D9AAC3DFA /* RoomChangePermissionsScreenModels.swift */; };
4DAEE2468669848B6C9F55B4 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33035418BB35754232985871 /* TimelineReadReceiptsView.swift */; }; 4DAEE2468669848B6C9F55B4 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33035418BB35754232985871 /* TimelineReadReceiptsView.swift */; };
4DEEFB73181C3B023DB42686 /* NetworkMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1575947B7A6FE08C57FE5EE4 /* NetworkMonitorProtocol.swift */; }; 4DEEFB73181C3B023DB42686 /* NetworkMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1575947B7A6FE08C57FE5EE4 /* NetworkMonitorProtocol.swift */; };
@ -448,6 +451,7 @@
64D05250CEDE8B604119F6E6 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981663D961C94270FA035FD0 /* Alert.swift */; }; 64D05250CEDE8B604119F6E6 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981663D961C94270FA035FD0 /* Alert.swift */; };
64E541F88F35BD126C4AFCA1 /* AppLockScreenPINKeypad.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38345442415E07A931197C55 /* AppLockScreenPINKeypad.swift */; }; 64E541F88F35BD126C4AFCA1 /* AppLockScreenPINKeypad.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38345442415E07A931197C55 /* AppLockScreenPINKeypad.swift */; };
64EE9D2CF7AD02EE53983CE1 /* FileRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F75EF13F49DD2204E760910 /* FileRoomTimelineView.swift */; }; 64EE9D2CF7AD02EE53983CE1 /* FileRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F75EF13F49DD2204E760910 /* FileRoomTimelineView.swift */; };
64F8590F4BEE4DA231F97D83 /* EncryptionResetFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07B011547B201A836C03052 /* EncryptionResetFlowCoordinator.swift */; };
651341E67C3514F9811A1EC1 /* LoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */; }; 651341E67C3514F9811A1EC1 /* LoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */; };
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1423AB065857FA546444DB15 /* NotificationManager.swift */; }; 652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1423AB065857FA546444DB15 /* NotificationManager.swift */; };
6530865EB9A8C0F0AF0216DA /* ServerSelectionScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9501D11B4258DFA33BA3B40F /* ServerSelectionScreenModels.swift */; }; 6530865EB9A8C0F0AF0216DA /* ServerSelectionScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9501D11B4258DFA33BA3B40F /* ServerSelectionScreenModels.swift */; };
@ -917,6 +921,7 @@
C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81B6170DB690013CEB646F4 /* MapLibreModels.swift */; }; C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81B6170DB690013CEB646F4 /* MapLibreModels.swift */; };
C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; }; C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; };
CA12AE0DCD57D49CD96C699A /* WaveformCursorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */; }; CA12AE0DCD57D49CD96C699A /* WaveformCursorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */; };
CACD1352927336F01FC76612 /* EncryptionResetUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4C76F31A382B8E4DD07583 /* EncryptionResetUITests.swift */; };
CB137BFB3E083C33E398A6CB /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 020597E28A4BC8E1BE8EDF6E /* KeychainAccess */; }; CB137BFB3E083C33E398A6CB /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 020597E28A4BC8E1BE8EDF6E /* KeychainAccess */; };
CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; }; CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; };
CB6956565D858C523E3E3B16 /* ComposerDraftServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E37072597F67C4DD8CC2DB /* ComposerDraftServiceProtocol.swift */; }; CB6956565D858C523E3E3B16 /* ComposerDraftServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E37072597F67C4DD8CC2DB /* ComposerDraftServiceProtocol.swift */; };
@ -1149,6 +1154,7 @@
FF34BF2AF731340AF9414A18 /* SwipeRightAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4552D3466B1453F287223ADA /* SwipeRightAction.swift */; }; FF34BF2AF731340AF9414A18 /* SwipeRightAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4552D3466B1453F287223ADA /* SwipeRightAction.swift */; };
FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */; }; FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */; };
FF9C06BBF6AC6F1CFFBEBFFC /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 90791B9C739C716A40E1B230 /* target.yml */; }; FF9C06BBF6AC6F1CFFBEBFFC /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 90791B9C739C716A40E1B230 /* target.yml */; };
FFD52DCDA6962055A363CC8F /* IdentityResetHandleSDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFB1D29B0870AFB6A56E9B8 /* IdentityResetHandleSDKMock.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -1475,6 +1481,7 @@
3BAC027034248429A438886B /* AppMediatorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediatorMock.swift; sourceTree = "<group>"; }; 3BAC027034248429A438886B /* AppMediatorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediatorMock.swift; sourceTree = "<group>"; };
3BC1B7CB061C9865B2B91B56 /* QRCodeLoginScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenViewModel.swift; sourceTree = "<group>"; }; 3BC1B7CB061C9865B2B91B56 /* QRCodeLoginScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenViewModel.swift; sourceTree = "<group>"; };
3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenCoordinator.swift; sourceTree = "<group>"; }; 3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenCoordinator.swift; sourceTree = "<group>"; };
3BF8E5D4C95974B96A18C80E /* EncryptionSettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionSettingsUITests.swift; sourceTree = "<group>"; };
3C368CAB3063EF275357ECD4 /* LoginScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModel.swift; sourceTree = "<group>"; }; 3C368CAB3063EF275357ECD4 /* LoginScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModel.swift; sourceTree = "<group>"; };
3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenModels.swift; sourceTree = "<group>"; }; 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenModels.swift; sourceTree = "<group>"; };
3CCD41CD67DB5DA0D436BFE9 /* VoiceMessageRoomPlaybackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomPlaybackView.swift; sourceTree = "<group>"; }; 3CCD41CD67DB5DA0D436BFE9 /* VoiceMessageRoomPlaybackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomPlaybackView.swift; sourceTree = "<group>"; };
@ -1553,6 +1560,7 @@
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; }; 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = "<group>"; };
4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; }; 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; };
4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = "<group>"; };
4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = "<group>"; }; 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = "<group>"; };
4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; }; 4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; };
4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = "<group>"; }; 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
@ -1632,6 +1640,7 @@
5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProtocol.swift; sourceTree = "<group>"; }; 5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProtocol.swift; sourceTree = "<group>"; };
5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = "<group>"; }; 5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = "<group>"; };
5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenCoordinator.swift; sourceTree = "<group>"; }; 5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenCoordinator.swift; sourceTree = "<group>"; };
5EFB1D29B0870AFB6A56E9B8 /* IdentityResetHandleSDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityResetHandleSDKMock.swift; sourceTree = "<group>"; };
5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; }; 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = "<group>"; }; 5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = "<group>"; };
@ -1906,6 +1915,7 @@
A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreenViewModel.swift; sourceTree = "<group>"; }; A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreenViewModel.swift; sourceTree = "<group>"; };
A02D1A490944BF01A37586E1 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/SAS.strings; sourceTree = "<group>"; }; A02D1A490944BF01A37586E1 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/SAS.strings; sourceTree = "<group>"; };
A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerProtocol.swift; sourceTree = "<group>"; }; A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerProtocol.swift; sourceTree = "<group>"; };
A07B011547B201A836C03052 /* EncryptionResetFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetFlowCoordinator.swift; sourceTree = "<group>"; };
A103580EBA06155B1343EF16 /* HomeScreenKnockedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenKnockedCell.swift; sourceTree = "<group>"; }; A103580EBA06155B1343EF16 /* HomeScreenKnockedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenKnockedCell.swift; sourceTree = "<group>"; };
A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerProtocol.swift; sourceTree = "<group>"; }; A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerProtocol.swift; sourceTree = "<group>"; };
A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; }; A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@ -2035,6 +2045,7 @@
BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; }; BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; };
BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = "<group>"; }; BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = "<group>"; };
BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = "<group>"; }; BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = "<group>"; };
BE4C76F31A382B8E4DD07583 /* EncryptionResetUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetUITests.swift; sourceTree = "<group>"; };
BE78CAD0B964C66FD06EF83E /* DeactivateAccountScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenModels.swift; sourceTree = "<group>"; }; BE78CAD0B964C66FD06EF83E /* DeactivateAccountScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenModels.swift; sourceTree = "<group>"; };
BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReplyDetails.swift; sourceTree = "<group>"; }; BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReplyDetails.swift; sourceTree = "<group>"; };
BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModel.swift; sourceTree = "<group>"; }; BE9BBB18FB27F09032AD8769 /* NotificationPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModel.swift; sourceTree = "<group>"; };
@ -2244,6 +2255,7 @@
EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = "<group>"; }; EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = "<group>"; };
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = "<group>"; }; EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = "<group>"; };
EC5D7DA665E1F5F509C994C7 /* ScaledOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledOffsetModifier.swift; sourceTree = "<group>"; }; EC5D7DA665E1F5F509C994C7 /* ScaledOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledOffsetModifier.swift; sourceTree = "<group>"; };
ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionSettingsFlowCoordinator.swift; sourceTree = "<group>"; };
ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; }; ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; }; ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfiguration.swift; sourceTree = "<group>"; }; ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfiguration.swift; sourceTree = "<group>"; };
@ -2935,6 +2947,7 @@
FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */, FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */,
D479DF730528153665E5782E /* RoomTimelineControllerFactoryMock.swift */, D479DF730528153665E5782E /* RoomTimelineControllerFactoryMock.swift */,
F74532E01B317C56C1BE8FA8 /* RoomTimelineProviderMock.swift */, F74532E01B317C56C1BE8FA8 /* RoomTimelineProviderMock.swift */,
4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */,
248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */, 248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */,
7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */, 7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */,
AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */, AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */,
@ -3531,6 +3544,8 @@
0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */, 0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */,
A9B069D7772DDF6513E0F1B8 /* AuthenticationFlowCoordinator.swift */, A9B069D7772DDF6513E0F1B8 /* AuthenticationFlowCoordinator.swift */,
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */, 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */,
A07B011547B201A836C03052 /* EncryptionResetFlowCoordinator.swift */,
ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */,
C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */, C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */,
A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */, A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */,
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */, 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
@ -4444,6 +4459,8 @@
295E28C3B9EAADF519BF2F44 /* AuthenticationFlowCoordinatorUITests.swift */, 295E28C3B9EAADF519BF2F44 /* AuthenticationFlowCoordinatorUITests.swift */,
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */, C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */,
F8CEB4634C0DD7779C4AB504 /* CreateRoomScreenUITests.swift */, F8CEB4634C0DD7779C4AB504 /* CreateRoomScreenUITests.swift */,
BE4C76F31A382B8E4DD07583 /* EncryptionResetUITests.swift */,
3BF8E5D4C95974B96A18C80E /* EncryptionSettingsUITests.swift */,
3368395F06AA180138E185B6 /* PollFormScreenUITests.swift */, 3368395F06AA180138E185B6 /* PollFormScreenUITests.swift */,
C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */, C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */,
45571C2EBD98ED7E0CEA7AF7 /* RoomRolesAndPermissionsUITests.swift */, 45571C2EBD98ED7E0CEA7AF7 /* RoomRolesAndPermissionsUITests.swift */,
@ -5287,6 +5304,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */, 8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */,
5EFB1D29B0870AFB6A56E9B8 /* IdentityResetHandleSDKMock.swift */,
E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */, E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */,
); );
path = SDK; path = SDK;
@ -6470,6 +6488,7 @@
03CDCA6243F89B194E3FAD17 /* EncryptionAuthenticity.swift in Sources */, 03CDCA6243F89B194E3FAD17 /* EncryptionAuthenticity.swift in Sources */,
FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */, FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */,
46A6DB0F78FB399BD59E2D41 /* EncryptionKeyProviderProtocol.swift in Sources */, 46A6DB0F78FB399BD59E2D41 /* EncryptionKeyProviderProtocol.swift in Sources */,
64F8590F4BEE4DA231F97D83 /* EncryptionResetFlowCoordinator.swift in Sources */,
0C6DF318E9C8F6461E6ABDE7 /* EncryptionResetPasswordScreen.swift in Sources */, 0C6DF318E9C8F6461E6ABDE7 /* EncryptionResetPasswordScreen.swift in Sources */,
36926D795D6D19177C7812F8 /* EncryptionResetPasswordScreenCoordinator.swift in Sources */, 36926D795D6D19177C7812F8 /* EncryptionResetPasswordScreenCoordinator.swift in Sources */,
B1B255CE0E4306DD6E09D936 /* EncryptionResetPasswordScreenModels.swift in Sources */, B1B255CE0E4306DD6E09D936 /* EncryptionResetPasswordScreenModels.swift in Sources */,
@ -6480,6 +6499,7 @@
97BAEDD9054FB5F233EE928B /* EncryptionResetScreenModels.swift in Sources */, 97BAEDD9054FB5F233EE928B /* EncryptionResetScreenModels.swift in Sources */,
EC3320639828BED8B3E5F2C6 /* EncryptionResetScreenViewModel.swift in Sources */, EC3320639828BED8B3E5F2C6 /* EncryptionResetScreenViewModel.swift in Sources */,
A0868BDE84D2140A885BE3C9 /* EncryptionResetScreenViewModelProtocol.swift in Sources */, A0868BDE84D2140A885BE3C9 /* EncryptionResetScreenViewModelProtocol.swift in Sources */,
4D2B54233C7B2C04B4ABE55A /* EncryptionSettingsFlowCoordinator.swift in Sources */,
50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */, 50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */,
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */, F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */,
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */, 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */,
@ -6527,6 +6547,7 @@
D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */, D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */,
01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */, 01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */,
AADE7C2497A7B55D8BED7BD6 /* IdentityConfirmedScreenViewModelProtocol.swift in Sources */, AADE7C2497A7B55D8BED7BD6 /* IdentityConfirmedScreenViewModelProtocol.swift in Sources */,
FFD52DCDA6962055A363CC8F /* IdentityResetHandleSDKMock.swift in Sources */,
BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */, BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */,
7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */, 7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */,
B796A25F282C0A340D1B9C12 /* ImageRoomTimelineItemContent.swift in Sources */, B796A25F282C0A340D1B9C12 /* ImageRoomTimelineItemContent.swift in Sources */,
@ -6868,6 +6889,7 @@
67160204A8D362BB7D4AD259 /* Search.swift in Sources */, 67160204A8D362BB7D4AD259 /* Search.swift in Sources */,
339BC18777912E1989F2F17D /* Section.swift in Sources */, 339BC18777912E1989F2F17D /* Section.swift in Sources */,
F833D5B5BE6707F961FA88DB /* SecureBackupController.swift in Sources */, F833D5B5BE6707F961FA88DB /* SecureBackupController.swift in Sources */,
2D0E3983288E2D35613AD681 /* SecureBackupControllerMock.swift in Sources */,
0C88044649BAEE6C49BFC43A /* SecureBackupControllerProtocol.swift in Sources */, 0C88044649BAEE6C49BFC43A /* SecureBackupControllerProtocol.swift in Sources */,
21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */, 21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */,
5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */, 5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */,
@ -7092,6 +7114,8 @@
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */, 7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */,
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */, 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */,
9F19096BFA629C0AC282B1E4 /* CreateRoomScreenUITests.swift in Sources */, 9F19096BFA629C0AC282B1E4 /* CreateRoomScreenUITests.swift in Sources */,
CACD1352927336F01FC76612 /* EncryptionResetUITests.swift in Sources */,
230981086F0199F913434D6B /* EncryptionSettingsUITests.swift in Sources */,
0CF81807BE5FBFC9E2BBCECF /* PollFormScreenUITests.swift in Sources */, 0CF81807BE5FBFC9E2BBCECF /* PollFormScreenUITests.swift in Sources */,
44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */, 44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */,
D29E046C1E3045E0346C479D /* RoomRolesAndPermissionsUITests.swift in Sources */, D29E046C1E3045E0346C479D /* RoomRolesAndPermissionsUITests.swift in Sources */,

View File

@ -0,0 +1,158 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Combine
import Foundation
import SwiftState
enum EncryptionResetFlowCoordinatorAction: Equatable {
/// The flow is complete.
case resetComplete
/// The flow was cancelled.
case cancel
}
struct EncryptionResetFlowCoordinatorParameters {
let userSession: UserSessionProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let navigationStackCoordinator: NavigationStackCoordinator
let windowManger: WindowManagerProtocol
}
class EncryptionResetFlowCoordinator: FlowCoordinatorProtocol {
private let userSession: UserSessionProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let navigationStackCoordinator: NavigationStackCoordinator
private let windowManager: WindowManagerProtocol
enum State: StateType {
/// The state machine hasn't started.
case initial
/// The root screen for this flow.
case encryptionResetScreen
/// Confirming the user's password to continue.
case confirmingPassword
}
enum Event: EventType {
/// The flow is being started.
case start
/// The user needs to confirm their password to reset.
case confirmPassword
/// The user confirmed their password.
case finishedConfirmingPassword
}
private let stateMachine: StateMachine<State, Event>
private var cancellables: Set<AnyCancellable> = []
private let actionsSubject: PassthroughSubject<EncryptionResetFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<EncryptionResetFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: EncryptionResetFlowCoordinatorParameters) {
userSession = parameters.userSession
userIndicatorController = parameters.userIndicatorController
navigationStackCoordinator = parameters.navigationStackCoordinator
windowManager = parameters.windowManger
stateMachine = .init(state: .initial)
configureStateMachine()
}
func start() {
stateMachine.tryEvent(.start)
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
// There aren't any routes to this screen, so always clear the stack.
clearRoute(animated: animated)
}
func clearRoute(animated: Bool) {
// As we push screens on top of an existing stack, popping to root wouldn't be safe.
switch stateMachine.state {
case .initial:
break
case .encryptionResetScreen:
navigationStackCoordinator.pop(animated: animated)
case .confirmingPassword:
navigationStackCoordinator.pop(animated: animated) // Password screen.
navigationStackCoordinator.pop(animated: animated) // EncryptionReset screen.
}
}
// MARK: - Private
private func configureStateMachine() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .encryptionResetScreen]) { [weak self] _ in
self?.presentEncryptionResetScreen()
}
stateMachine.addRoutes(event: .confirmPassword, transitions: [.encryptionResetScreen => .confirmingPassword]) { [weak self] context in
guard let passwordPublisher = context.userInfo as? PassthroughSubject<String, Never> else { fatalError("Expected a publisher in the userInfo.") }
self?.presentPasswordScreen(passwordPublisher: passwordPublisher)
}
stateMachine.addRoutes(event: .finishedConfirmingPassword, transitions: [.confirmingPassword => .encryptionResetScreen])
stateMachine.addErrorHandler { context in
fatalError("Unexpected transition: \(context)")
}
}
private func presentEncryptionResetScreen() {
let coordinator = EncryptionResetScreenCoordinator(parameters: .init(clientProxy: userSession.clientProxy,
userIndicatorController: userIndicatorController))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .requestOIDCAuthorisation(let url):
presentOIDCAuthorization(for: url)
case .requestPassword(let passwordPublisher):
stateMachine.tryEvent(.confirmPassword, userInfo: passwordPublisher)
case .cancel:
actionsSubject.send(.cancel)
case .resetFinished:
actionsSubject.send(.resetComplete)
}
}
.store(in: &cancellables)
navigationStackCoordinator.setRootCoordinator(coordinator)
}
private func presentPasswordScreen(passwordPublisher: PassthroughSubject<String, Never>) {
let coordinator = EncryptionResetPasswordScreenCoordinator(parameters: .init(passwordPublisher: passwordPublisher))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .passwordEntered:
navigationStackCoordinator.pop()
}
}
.store(in: &cancellables)
navigationStackCoordinator.push(coordinator) { [stateMachine] in
stateMachine.tryEvent(.finishedConfirmingPassword)
}
}
private var accountSettingsPresenter: OIDCAccountSettingsPresenter?
private func presentOIDCAuthorization(for url: URL) {
// Note to anyone in the future if you come back here to make this open in Safari instead of a WAS.
// As of iOS 16, there is an issue on the simulator with accessing the cookie but it works on a device. 🤷
accountSettingsPresenter = OIDCAccountSettingsPresenter(accountURL: url, presentationAnchor: windowManager.mainWindow)
accountSettingsPresenter?.start()
}
}

View File

@ -0,0 +1,198 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Combine
import Foundation
import SwiftState
enum EncryptionSettingsFlowCoordinatorAction: Equatable {
/// The flow is complete.
case complete
}
struct EncryptionSettingsFlowCoordinatorParameters {
let userSession: UserSessionProtocol
let appSettings: AppSettings
let userIndicatorController: UserIndicatorControllerProtocol
let navigationStackCoordinator: NavigationStackCoordinator
}
class EncryptionSettingsFlowCoordinator: FlowCoordinatorProtocol {
private let userSession: UserSessionProtocol
private let appSettings: AppSettings
private let userIndicatorController: UserIndicatorControllerProtocol
private let navigationStackCoordinator: NavigationStackCoordinator
// periphery:ignore - retaining purpose
private var encryptionResetFlowCoordinator: EncryptionResetFlowCoordinator?
enum State: StateType {
/// The state machine hasn't started.
case initial
/// The root screen for this flow.
case secureBackupScreen
/// The user is managing their recovery key.
case recoveryKeyScreen
/// The user is disabling key backups.
case keyBackupScreen
}
enum Event: EventType {
/// The flow is being started.
case start
/// The user would like to manage their recovery key.
case manageRecoveryKey
/// The user finished managing their recovery key.
case finishedManagingRecoveryKey
/// The user doesn't want to use key backup any more.
case disableKeyBackup
/// The key backup screen was dismissed.
case finishedDisablingKeyBackup
}
private let stateMachine: StateMachine<State, Event>
private var cancellables: Set<AnyCancellable> = []
private let actionsSubject: PassthroughSubject<EncryptionSettingsFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<EncryptionSettingsFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: EncryptionSettingsFlowCoordinatorParameters) {
userSession = parameters.userSession
appSettings = parameters.appSettings
userIndicatorController = parameters.userIndicatorController
navigationStackCoordinator = parameters.navigationStackCoordinator
stateMachine = .init(state: .initial)
configureStateMachine()
}
func start() {
stateMachine.tryEvent(.start)
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
switch appRoute {
case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias,
.roomDetails, .roomMemberDetails, .userProfile,
.event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias,
.call, .genericCallLink, .settings:
// These routes aren't in this flow so clear the entire stack.
clearRoute(animated: animated)
case .chatBackupSettings:
popToRootScreen(animated: animated)
}
}
func clearRoute(animated: Bool) {
let fromState = stateMachine.state
popToRootScreen(animated: animated)
guard fromState != .initial else { return }
navigationStackCoordinator.pop(animated: animated) // SecureBackup screen.
}
func popToRootScreen(animated: Bool) {
// As we push screens on top of an existing stack, a literal pop to root wouldn't be safe.
switch stateMachine.state {
case .initial, .secureBackupScreen:
break
case .recoveryKeyScreen:
navigationStackCoordinator.setSheetCoordinator(nil, animated: animated) // RecoveryKey screen.
case .keyBackupScreen:
navigationStackCoordinator.setSheetCoordinator(nil, animated: animated) // KeyBackup screen.
}
}
// MARK: - Private
private func configureStateMachine() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .secureBackupScreen]) { [weak self] _ in
self?.presentSecureBackupScreen()
}
stateMachine.addRoutes(event: .manageRecoveryKey, transitions: [.secureBackupScreen => .recoveryKeyScreen]) { [weak self] _ in
self?.presentRecoveryKeyScreen()
}
stateMachine.addRoutes(event: .finishedManagingRecoveryKey, transitions: [.recoveryKeyScreen => .secureBackupScreen])
stateMachine.addRoutes(event: .disableKeyBackup, transitions: [.secureBackupScreen => .keyBackupScreen]) { [weak self] _ in
self?.presentKeyBackupScreen()
}
stateMachine.addRoutes(event: .finishedDisablingKeyBackup, transitions: [.keyBackupScreen => .secureBackupScreen])
stateMachine.addErrorHandler { context in
fatalError("Unexpected transition: \(context)")
}
}
private func presentSecureBackupScreen(animated: Bool = true) {
let coordinator = SecureBackupScreenCoordinator(parameters: .init(appSettings: appSettings,
clientProxy: userSession.clientProxy,
userIndicatorController: userIndicatorController))
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .manageRecoveryKey:
stateMachine.tryEvent(.manageRecoveryKey)
case .disableKeyBackup:
stateMachine.tryEvent(.disableKeyBackup)
}
}
.store(in: &cancellables)
navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
self?.actionsSubject.send(.complete)
}
}
private func presentRecoveryKeyScreen() {
let sheetNavigationStackCoordinator = NavigationStackCoordinator()
let coordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
userIndicatorController: userIndicatorController,
isModallyPresented: true))
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .complete:
navigationStackCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
sheetNavigationStackCoordinator.setRootCoordinator(coordinator, animated: true)
navigationStackCoordinator.setSheetCoordinator(sheetNavigationStackCoordinator) { [stateMachine] in
stateMachine.tryEvent(.finishedManagingRecoveryKey)
}
}
private func presentKeyBackupScreen() {
let sheetNavigationStackCoordinator = NavigationStackCoordinator()
let coordinator = SecureBackupKeyBackupScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
userIndicatorController: userIndicatorController))
coordinator.actions.sink { [weak self] action in
switch action {
case .done:
self?.navigationStackCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
sheetNavigationStackCoordinator.setRootCoordinator(coordinator, animated: true)
navigationStackCoordinator.setSheetCoordinator(sheetNavigationStackCoordinator) { [stateMachine] in
stateMachine.tryEvent(.finishedDisablingKeyBackup)
}
}
}

View File

@ -46,6 +46,8 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
// periphery: ignore - used to store the coordinator to avoid deallocation // periphery: ignore - used to store the coordinator to avoid deallocation
private var appLockFlowCoordinator: AppLockSetupFlowCoordinator? private var appLockFlowCoordinator: AppLockSetupFlowCoordinator?
// periphery: ignore - used to store the coordinator to avoid deallocation
private var encryptionResetFlowCoordinator: EncryptionResetFlowCoordinator?
private let actionsSubject: PassthroughSubject<OnboardingFlowCoordinatorAction, Never> = .init() private let actionsSubject: PassthroughSubject<OnboardingFlowCoordinatorAction, Never> = .init()
var actions: AnyPublisher<OnboardingFlowCoordinatorAction, Never> { var actions: AnyPublisher<OnboardingFlowCoordinatorAction, Never> {
@ -251,7 +253,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
appSettings.hasRunIdentityConfirmationOnboarding = true appSettings.hasRunIdentityConfirmationOnboarding = true
stateMachine.tryEvent(.nextSkippingIdentityConfimed) stateMachine.tryEvent(.nextSkippingIdentityConfimed)
case .reset: case .reset:
presentEncryptionResetScreen() startEncryptionResetFlow()
case .logout: case .logout:
actionsSubject.send(.logout) actionsSubject.send(.logout)
} }
@ -295,12 +297,8 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return } guard let self else { return }
switch action { switch action {
case .recoveryFixed: case .complete:
break // Moving to next state is Handled by the global session verification listener break // Moving to next state is Handled by the global session verification listener
case .resetEncryption:
presentEncryptionResetScreen()
default:
MXLog.error("Unexpected recovery action: \(action)")
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@ -308,31 +306,31 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
presentCoordinator(coordinator) presentCoordinator(coordinator)
} }
private func presentEncryptionResetScreen() { private func startEncryptionResetFlow() {
let resetNavigationStackCoordinator = NavigationStackCoordinator() let resetNavigationStackCoordinator = NavigationStackCoordinator()
let coordinator = EncryptionResetFlowCoordinator(parameters: .init(userSession: userSession,
let coordinator = EncryptionResetScreenCoordinator(parameters: .init(clientProxy: userSession.clientProxy, userIndicatorController: userIndicatorController,
navigationStackCoordinator: resetNavigationStackCoordinator, navigationStackCoordinator: resetNavigationStackCoordinator,
userIndicatorController: userIndicatorController)) windowManger: windowManager))
coordinator.actionsPublisher.sink { [weak self] action in coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return } guard let self else { return }
switch action { switch action {
case .cancel: case .resetComplete:
navigationStackCoordinator.setSheetCoordinator(nil)
case .requestOIDCAuthorisation(let url):
presentOIDCAuthorisationScreen(url: url)
case .resetFinished:
// Moving to next state is handled by the global session verification listener // Moving to next state is handled by the global session verification listener
navigationStackCoordinator.setSheetCoordinator(nil) navigationStackCoordinator.setSheetCoordinator(nil)
case .cancel:
navigationStackCoordinator.setSheetCoordinator(nil)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
resetNavigationStackCoordinator.setRootCoordinator(coordinator) encryptionResetFlowCoordinator = coordinator
coordinator.start()
navigationStackCoordinator.setSheetCoordinator(resetNavigationStackCoordinator) navigationStackCoordinator.setSheetCoordinator(resetNavigationStackCoordinator) { [weak self] in
self?.encryptionResetFlowCoordinator = nil
}
} }
private func presentIdentityConfirmedScreen() { private func presentIdentityConfirmedScreen() {
@ -411,12 +409,4 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.push(coordinator, dismissalCallback: dismissalCallback) navigationStackCoordinator.push(coordinator, dismissalCallback: dismissalCallback)
} }
} }
private var accountSettingsPresenter: OIDCAccountSettingsPresenter?
private func presentOIDCAuthorisationScreen(url: URL) {
// Note to anyone in the future if you come back here to make this open in Safari instead of a WAS.
// As of iOS 16, there is an issue on the simulator with accessing the cookie but it works on a device. 🤷
accountSettingsPresenter = OIDCAccountSettingsPresenter(accountURL: url, presentationAnchor: windowManager.mainWindow)
accountSettingsPresenter?.start()
}
} }

View File

@ -39,9 +39,10 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
// periphery:ignore - retaining purpose // periphery:ignore - retaining purpose
private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator? private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator?
// periphery:ignore - retaining purpose // periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator? private var bugReportFlowCoordinator: BugReportFlowCoordinator?
// periphery:ignore - retaining purpose
private var encryptionSettingsFlowCoordinator: EncryptionSettingsFlowCoordinator?
private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init() private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init()
var actions: AnyPublisher<SettingsFlowCoordinatorAction, Never> { var actions: AnyPublisher<SettingsFlowCoordinatorAction, Never> {
@ -68,7 +69,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
// The navigation stack doesn't like it if the root and the push happen // The navigation stack doesn't like it if the root and the push happen
// on the same loop run // on the same loop run
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
self.presentSecureBackupScreen(animated: animated) self.startEncryptionSettingsFlow(animated: animated)
} }
default: default:
break break
@ -102,7 +103,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
self.actionsSubject.send(.runLogoutFlow) self.actionsSubject.send(.runLogoutFlow)
} }
case .secureBackup: case .secureBackup:
presentSecureBackupScreen(animated: true) startEncryptionSettingsFlow(animated: true)
case .userDetails: case .userDetails:
presentUserDetailsEditScreen() presentUserDetailsEditScreen()
case let .manageAccount(url): case let .manageAccount(url):
@ -145,21 +146,22 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
actionsSubject.send(.presentedSettings) actionsSubject.send(.presentedSettings)
} }
private func presentSecureBackupScreen(animated: Bool) { private func startEncryptionSettingsFlow(animated: Bool) {
let coordinator = SecureBackupScreenCoordinator(parameters: .init(appSettings: parameters.appSettings, let coordinator = EncryptionSettingsFlowCoordinator(parameters: .init(userSession: parameters.userSession,
clientProxy: parameters.userSession.clientProxy, appSettings: parameters.appSettings,
navigationStackCoordinator: navigationStackCoordinator, userIndicatorController: parameters.userIndicatorController,
userIndicatorController: parameters.userIndicatorController)) navigationStackCoordinator: navigationStackCoordinator))
coordinator.actionsPublisher.sink { [weak self] action in
coordinator.actions.sink { [weak self] action in
switch action { switch action {
case .requestOIDCAuthorisation(let url): case .complete:
self?.presentAccountManagementURL(url) // The flow coordinator tidies up the stack, no need to do anything.
self?.encryptionSettingsFlowCoordinator = nil
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
navigationStackCoordinator.push(coordinator, animated: animated) encryptionSettingsFlowCoordinator = coordinator
coordinator.start()
} }
private func presentUserDetailsEditScreen() { private func presentUserDetailsEditScreen() {

View File

@ -13,6 +13,8 @@ struct ClientProxyMockConfiguration {
var deviceID: String? var deviceID: String?
var roomSummaryProvider: RoomSummaryProviderProtocol? = RoomSummaryProviderMock(.init()) var roomSummaryProvider: RoomSummaryProviderProtocol? = RoomSummaryProviderMock(.init())
var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol? var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol?
var recoveryState: SecureBackupRecoveryState = .enabled
} }
enum ClientProxyMockError: Error { enum ClientProxyMockError: Error {
@ -78,12 +80,8 @@ extension ClientProxyMock {
loadMediaThumbnailForSourceWidthHeightThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) loadMediaThumbnailForSourceWidthHeightThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic)
loadMediaFileForSourceFilenameThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) loadMediaFileForSourceFilenameThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic)
secureBackupController = { secureBackupController = SecureBackupControllerMock(.init(recoveryState: configuration.recoveryState))
let secureBackupController = SecureBackupControllerMock() resetIdentityReturnValue = .success(IdentityResetHandleSDKMock(.init()))
secureBackupController.underlyingRecoveryState = .init(CurrentValueSubject<SecureBackupRecoveryState, Never>(.enabled))
secureBackupController.underlyingKeyBackupState = .init(CurrentValueSubject<SecureBackupKeyBackupState, Never>(.enabled))
return secureBackupController
}()
roomForIdentifierClosure = { [weak self] identifier in roomForIdentifierClosure = { [weak self] identifier in
guard let room = self?.roomSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else { guard let room = self?.roomSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {

View File

@ -0,0 +1,22 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Foundation
import MatrixRustSDK
extension IdentityResetHandleSDKMock {
struct Configuration { }
convenience init(_ configuration: Configuration) {
self.init()
authTypeReturnValue = .uiaa
resetAuthClosure = { _ in
try await Task.sleep(for: .seconds(60))
}
}
}

View File

@ -0,0 +1,48 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Combine
import Foundation
extension SecureBackupControllerMock {
struct Configuration {
var recoveryState: SecureBackupRecoveryState = .enabled
var keyBackupState: SecureBackupKeyBackupState = .enabled
}
convenience init(_ configuration: Configuration) {
self.init()
let recoveryStateSubject = CurrentValueSubject<SecureBackupRecoveryState, Never>(configuration.recoveryState)
underlyingRecoveryState = .init(recoveryStateSubject)
let keyBackupStateSubject = CurrentValueSubject<SecureBackupKeyBackupState, Never>(configuration.keyBackupState)
underlyingKeyBackupState = .init(keyBackupStateSubject)
disableClosure = {
recoveryStateSubject.send(.disabled)
keyBackupStateSubject.send(.unknown)
return .success(())
}
enableClosure = {
recoveryStateSubject.send(.disabled)
keyBackupStateSubject.send(.enabled)
return .success(())
}
generateRecoveryKeyClosure = {
recoveryStateSubject.send(.enabled)
return .success("a1B2 C3d4 E5F6 g7H8 i9J0 K1l2 M3n4 O5p6 Q7R8 s9T0 U1v2 W3X4")
}
confirmRecoveryKeyClosure = { _ in
recoveryStateSubject.send(.enabled)
return .success(())
}
}
}

View File

@ -16,6 +16,8 @@ enum A11yIdentifiers {
static let appLockSetupSettingsScreen = AppLockSetupSettingsScreen() static let appLockSetupSettingsScreen = AppLockSetupSettingsScreen()
static let bugReportScreen = BugReportScreen() static let bugReportScreen = BugReportScreen()
static let changeServerScreen = ChangeServer() static let changeServerScreen = ChangeServer()
static let encryptionResetScreen = EncryptionResetScreen()
static let encryptionResetPasswordScreen = EncryptionResetPasswordScreen()
static let homeScreen = HomeScreen() static let homeScreen = HomeScreen()
static let loginScreen = LoginScreen() static let loginScreen = LoginScreen()
static let authenticationStartScreen = AuthenticationStartScreen() static let authenticationStartScreen = AuthenticationStartScreen()
@ -24,6 +26,9 @@ enum A11yIdentifiers {
static let roomDetailsScreen = RoomDetailsScreen() static let roomDetailsScreen = RoomDetailsScreen()
static let roomNotificationSettingsScreen = RoomNotificationSettingsScreen() static let roomNotificationSettingsScreen = RoomNotificationSettingsScreen()
static let roomRolesAndPermissionsScreen = RoomRolesAndPermissionsScreen() static let roomRolesAndPermissionsScreen = RoomRolesAndPermissionsScreen()
static let secureBackupScreen = SecureBackupScreen()
static let secureBackupKeyBackupScreen = SecureBackupKeyBackupScreen()
static let secureBackupRecoveryKeyScreen = SecureBackupRecoveryKeyScreen()
static let serverConfirmationScreen = ServerConfirmationScreen() static let serverConfirmationScreen = ServerConfirmationScreen()
static let sessionVerificationScreen = SessionVerificationScreen() static let sessionVerificationScreen = SessionVerificationScreen()
static let settingsScreen = SettingsScreen() static let settingsScreen = SettingsScreen()
@ -81,6 +86,15 @@ enum A11yIdentifiers {
let dismiss = "change_server-dismiss" let dismiss = "change_server-dismiss"
} }
struct EncryptionResetScreen {
let continueReset = "encryption_reset-continue_reset"
}
struct EncryptionResetPasswordScreen {
let passwordField = "encryption_reset_password-password_field"
let submit = "encryption_reset_password-submit"
}
struct HomeScreen { struct HomeScreen {
let userAvatar = "home_screen-user_avatar" let userAvatar = "home_screen-user_avatar"
let recoveryKeyConfirmationBannerContinue = "home_screen-recovery_key_confirmation_continue" let recoveryKeyConfirmationBannerContinue = "home_screen-recovery_key_confirmation_continue"
@ -178,6 +192,23 @@ enum A11yIdentifiers {
let memberModeration = "room_roles_and_permissions-member_moderation" let memberModeration = "room_roles_and_permissions-member_moderation"
} }
struct SecureBackupScreen {
let keyStorage = "secure_backup-key_storage"
let recoveryKey = "secure_backup-recovery_key"
}
struct SecureBackupKeyBackupScreen {
let deleteKeyStorage = "secure_backup_key_backup-delete_key_storage"
}
struct SecureBackupRecoveryKeyScreen {
let generateRecoveryKey = "secure_backup_recovery_key-generate_recovery_key"
let copyRecoveryKey = "secure_backup_recovery_key-copy_recovery_key"
let done = "secure_backup_recovery_key-done"
let recoveryKeyField = "secure_backup_recovery_key-recovery_key_field"
let confirm = "secure_backup_recovery_key-confirm"
}
struct ServerConfirmationScreen { struct ServerConfirmationScreen {
let `continue` = "server_confirmation-continue" let `continue` = "server_confirmation-continue"
let changeServer = "server_confirmation-change_server" let changeServer = "server_confirmation-change_server"

View File

@ -8,17 +8,12 @@
import Combine import Combine
import SwiftUI import SwiftUI
struct EncryptionResetPasswordScreenCoordinatorParameters { } struct EncryptionResetPasswordScreenCoordinatorParameters {
let passwordPublisher: PassthroughSubject<String, Never>
}
enum EncryptionResetPasswordScreenCoordinatorAction: CustomStringConvertible { enum EncryptionResetPasswordScreenCoordinatorAction {
case resetIdentity(String) case passwordEntered
var description: String {
switch self {
case .resetIdentity:
"resetIdentity"
}
}
} }
final class EncryptionResetPasswordScreenCoordinator: CoordinatorProtocol { final class EncryptionResetPasswordScreenCoordinator: CoordinatorProtocol {
@ -35,7 +30,7 @@ final class EncryptionResetPasswordScreenCoordinator: CoordinatorProtocol {
init(parameters: EncryptionResetPasswordScreenCoordinatorParameters) { init(parameters: EncryptionResetPasswordScreenCoordinatorParameters) {
self.parameters = parameters self.parameters = parameters
viewModel = EncryptionResetPasswordScreenViewModel() viewModel = EncryptionResetPasswordScreenViewModel(passwordPublisher: parameters.passwordPublisher)
} }
func start() { func start() {
@ -44,8 +39,8 @@ final class EncryptionResetPasswordScreenCoordinator: CoordinatorProtocol {
guard let self else { return } guard let self else { return }
switch action { switch action {
case .resetIdentity(let password): case .passwordEntered:
self.actionsSubject.send(.resetIdentity(password)) self.actionsSubject.send(.passwordEntered)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)

View File

@ -7,15 +7,8 @@
import Foundation import Foundation
enum EncryptionResetPasswordScreenViewModelAction: CustomStringConvertible { enum EncryptionResetPasswordScreenViewModelAction {
case resetIdentity(String) case passwordEntered
var description: String {
switch self {
case .resetIdentity:
"resetIdentity"
}
}
} }
struct EncryptionResetPasswordScreenViewState: BindableState { struct EncryptionResetPasswordScreenViewState: BindableState {
@ -28,5 +21,5 @@ struct EncryptionResetPasswordScreenViewStateBindings {
} }
enum EncryptionResetPasswordScreenViewAction { enum EncryptionResetPasswordScreenViewAction {
case resetIdentity case submit
} }

View File

@ -11,12 +11,16 @@ import SwiftUI
typealias EncryptionResetPasswordScreenViewModelType = StateStoreViewModel<EncryptionResetPasswordScreenViewState, EncryptionResetPasswordScreenViewAction> typealias EncryptionResetPasswordScreenViewModelType = StateStoreViewModel<EncryptionResetPasswordScreenViewState, EncryptionResetPasswordScreenViewAction>
class EncryptionResetPasswordScreenViewModel: EncryptionResetPasswordScreenViewModelType, EncryptionResetPasswordScreenViewModelProtocol { class EncryptionResetPasswordScreenViewModel: EncryptionResetPasswordScreenViewModelType, EncryptionResetPasswordScreenViewModelProtocol {
private let passwordPublisher: PassthroughSubject<String, Never>
private let actionsSubject: PassthroughSubject<EncryptionResetPasswordScreenViewModelAction, Never> = .init() private let actionsSubject: PassthroughSubject<EncryptionResetPasswordScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<EncryptionResetPasswordScreenViewModelAction, Never> { var actionsPublisher: AnyPublisher<EncryptionResetPasswordScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
init() { init(passwordPublisher: PassthroughSubject<String, Never>) {
self.passwordPublisher = passwordPublisher
super.init(initialViewState: .init(bindings: .init(password: ""))) super.init(initialViewState: .init(bindings: .init(password: "")))
} }
@ -26,8 +30,9 @@ class EncryptionResetPasswordScreenViewModel: EncryptionResetPasswordScreenViewM
MXLog.info("View model: received view action: \(viewAction)") MXLog.info("View model: received view action: \(viewAction)")
switch viewAction { switch viewAction {
case .resetIdentity: case .submit:
actionsSubject.send(.resetIdentity(state.bindings.password)) passwordPublisher.send(state.bindings.password)
actionsSubject.send(.passwordEntered)
} }
} }
} }

View File

@ -5,6 +5,7 @@
// Please see LICENSE in the repository root for full details. // Please see LICENSE in the repository root for full details.
// //
import Combine
import Compound import Compound
import SwiftUI import SwiftUI
@ -32,9 +33,10 @@ struct EncryptionResetPasswordScreen: View {
.padding(16) .padding(16)
} bottomContent: { } bottomContent: {
Button(L10n.actionResetIdentity, role: .destructive) { Button(L10n.actionResetIdentity, role: .destructive) {
context.send(viewAction: .resetIdentity) context.send(viewAction: .submit)
} }
.buttonStyle(.compound(.primary)) .buttonStyle(.compound(.primary))
.accessibilityIdentifier(A11yIdentifiers.encryptionResetPasswordScreen.submit)
} }
.background() .background()
.backgroundStyle(.compound.bgCanvasDefault) .backgroundStyle(.compound.bgCanvasDefault)
@ -58,8 +60,9 @@ struct EncryptionResetPasswordScreen: View {
.focused($textFieldFocus) .focused($textFieldFocus)
.submitLabel(.done) .submitLabel(.done)
.onSubmit { .onSubmit {
context.send(viewAction: .resetIdentity) context.send(viewAction: .submit)
} }
.accessibilityIdentifier(A11yIdentifiers.encryptionResetPasswordScreen.passwordField)
} }
} }
} }
@ -67,7 +70,8 @@ struct EncryptionResetPasswordScreen: View {
// MARK: - Previews // MARK: - Previews
struct EncryptionResetPasswordScreen_Previews: PreviewProvider, TestablePreview { struct EncryptionResetPasswordScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = EncryptionResetPasswordScreenViewModel() static let passwordPublisher = PassthroughSubject<String, Never>()
static let viewModel = EncryptionResetPasswordScreenViewModel(passwordPublisher: passwordPublisher)
static var previews: some View { static var previews: some View {
NavigationStack { NavigationStack {
EncryptionResetPasswordScreen(context: viewModel.context) EncryptionResetPasswordScreen(context: viewModel.context)

View File

@ -9,14 +9,14 @@ import Combine
import SwiftUI import SwiftUI
enum EncryptionResetScreenCoordinatorAction { enum EncryptionResetScreenCoordinatorAction {
case cancel
case requestOIDCAuthorisation(URL) case requestOIDCAuthorisation(URL)
case requestPassword(passwordPublisher: PassthroughSubject<String, Never>)
case resetFinished case resetFinished
case cancel
} }
struct EncryptionResetScreenCoordinatorParameters { struct EncryptionResetScreenCoordinatorParameters {
let clientProxy: ClientProxyProtocol let clientProxy: ClientProxyProtocol
let navigationStackCoordinator: NavigationStackCoordinator
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
} }
@ -43,10 +43,10 @@ final class EncryptionResetScreenCoordinator: CoordinatorProtocol {
guard let self else { return } guard let self else { return }
switch action { switch action {
case .requestPassword:
presentPasswordScreen()
case .requestOIDCAuthorisation(let url): case .requestOIDCAuthorisation(let url):
self.actionsSubject.send(.requestOIDCAuthorisation(url)) self.actionsSubject.send(.requestOIDCAuthorisation(url))
case .requestPassword(let passwordPublisher):
self.actionsSubject.send(.requestPassword(passwordPublisher: passwordPublisher))
case .resetFinished: case .resetFinished:
self.actionsSubject.send(.resetFinished) self.actionsSubject.send(.resetFinished)
case .cancel: case .cancel:
@ -63,23 +63,4 @@ final class EncryptionResetScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView { func toPresentable() -> AnyView {
AnyView(EncryptionResetScreen(context: viewModel.context)) AnyView(EncryptionResetScreen(context: viewModel.context))
} }
// MARK: - Private
private func presentPasswordScreen() {
let coordinator = EncryptionResetPasswordScreenCoordinator(parameters: .init())
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .resetIdentity(let password):
viewModel.continueResetFlowWith(password: password)
parameters.navigationStackCoordinator.pop()
}
}
.store(in: &cancellables)
parameters.navigationStackCoordinator.push(coordinator)
}
} }

View File

@ -5,10 +5,11 @@
// Please see LICENSE in the repository root for full details. // Please see LICENSE in the repository root for full details.
// //
import Combine
import Foundation import Foundation
enum EncryptionResetScreenViewModelAction { enum EncryptionResetScreenViewModelAction {
case requestPassword case requestPassword(passwordPublisher: PassthroughSubject<String, Never>)
case requestOIDCAuthorisation(url: URL) case requestOIDCAuthorisation(url: URL)
case resetFinished case resetFinished
case cancel case cancel

View File

@ -21,6 +21,7 @@ class EncryptionResetScreenViewModel: EncryptionResetScreenViewModelType, Encryp
} }
private var identityResetHandle: IdentityResetHandle? private var identityResetHandle: IdentityResetHandle?
private var passwordCancellable: AnyCancellable?
init(clientProxy: ClientProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) { init(clientProxy: ClientProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) {
self.clientProxy = clientProxy self.clientProxy = clientProxy
@ -46,12 +47,6 @@ class EncryptionResetScreenViewModel: EncryptionResetScreenViewModelType, Encryp
} }
} }
func continueResetFlowWith(password: String) {
Task {
await resetWith(password: password)
}
}
func stop() { func stop() {
Task { Task {
await identityResetHandle?.cancel() await identityResetHandle?.cancel()
@ -80,7 +75,14 @@ class EncryptionResetScreenViewModel: EncryptionResetScreenViewModelType, Encryp
switch handle.authType() { switch handle.authType() {
case .uiaa: case .uiaa:
actionsSubject.send(.requestPassword) let passwordPublisher = PassthroughSubject<String, Never>()
passwordCancellable = passwordPublisher.sink { [weak self] password in
guard let self else { return }
passwordCancellable = nil
Task { await self.resetWith(password: password) }
}
actionsSubject.send(.requestPassword(passwordPublisher: passwordPublisher))
case .oidc(let oidcInfo): case .oidc(let oidcInfo):
guard let url = URL(string: oidcInfo.approvalUrl) else { guard let url = URL(string: oidcInfo.approvalUrl) else {
fatalError("Invalid URL received through identity reset handle: \(oidcInfo.approvalUrl)") fatalError("Invalid URL received through identity reset handle: \(oidcInfo.approvalUrl)")

View File

@ -12,6 +12,5 @@ protocol EncryptionResetScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<EncryptionResetScreenViewModelAction, Never> { get } var actionsPublisher: AnyPublisher<EncryptionResetScreenViewModelAction, Never> { get }
var context: EncryptionResetScreenViewModelType.Context { get } var context: EncryptionResetScreenViewModelType.Context { get }
func continueResetFlowWith(password: String)
func stop() func stop()
} }

View File

@ -19,6 +19,7 @@ struct EncryptionResetScreen: View {
context.send(viewAction: .reset) context.send(viewAction: .reset)
} }
.buttonStyle(.compound(.primary)) .buttonStyle(.compound(.primary))
.accessibilityIdentifier(A11yIdentifiers.encryptionResetScreen.continueReset)
} }
.background() .background()
.backgroundStyle(.compound.bgSubtleSecondary) .backgroundStyle(.compound.bgSubtleSecondary)

View File

@ -22,6 +22,7 @@ struct SecureBackupKeyBackupScreen: View {
Text(L10n.screenChatBackupKeyBackupActionDisable) Text(L10n.screenChatBackupKeyBackupActionDisable)
} }
.buttonStyle(.compound(.primary)) .buttonStyle(.compound(.primary))
.accessibilityIdentifier(A11yIdentifiers.secureBackupKeyBackupScreen.deleteKeyStorage)
} }
.background() .background()
.backgroundStyle(.compound.bgCanvasDefault) .backgroundStyle(.compound.bgCanvasDefault)

View File

@ -15,23 +15,22 @@ struct SecureBackupRecoveryKeyScreenCoordinatorParameters {
} }
enum SecureBackupRecoveryKeyScreenCoordinatorAction { enum SecureBackupRecoveryKeyScreenCoordinatorAction {
case cancel case complete
case recoverySetUp
case recoveryChanged
case recoveryFixed
case resetEncryption
} }
final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol { final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
private let parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters
private var viewModel: SecureBackupRecoveryKeyScreenViewModelProtocol private var viewModel: SecureBackupRecoveryKeyScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> = .init()
var actions: AnyPublisher<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> { var actions: AnyPublisher<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
init(parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters) { init(parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = SecureBackupRecoveryKeyScreenViewModel(secureBackupController: parameters.secureBackupController, viewModel = SecureBackupRecoveryKeyScreenViewModel(secureBackupController: parameters.secureBackupController,
userIndicatorController: parameters.userIndicatorController, userIndicatorController: parameters.userIndicatorController,
isModallyPresented: parameters.isModallyPresented) isModallyPresented: parameters.isModallyPresented)
@ -44,20 +43,19 @@ final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
guard let self else { return } guard let self else { return }
switch action { switch action {
case .cancel: case .cancel:
self.actionsSubject.send(.cancel) self.actionsSubject.send(.complete)
case .done(let mode): case .done(let mode):
switch mode { switch mode {
case .setupRecovery: case .setupRecovery:
self.actionsSubject.send(.recoverySetUp) showSuccessIndicator(title: L10n.screenRecoveryKeySetupSuccess)
case .changeRecovery: case .changeRecovery:
self.actionsSubject.send(.recoveryChanged) showSuccessIndicator(title: L10n.screenRecoveryKeyChangeSuccess)
case .fixRecovery: case .fixRecovery:
self.actionsSubject.send(.recoveryFixed) showSuccessIndicator(title: L10n.screenRecoveryKeyConfirmSuccess)
case .unknown: case .unknown:
fatalError() fatalError()
} }
case .resetEncryption: self.actionsSubject.send(.complete)
self.actionsSubject.send(.resetEncryption)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@ -66,4 +64,14 @@ final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView { func toPresentable() -> AnyView {
AnyView(SecureBackupRecoveryKeyScreen(context: viewModel.context)) AnyView(SecureBackupRecoveryKeyScreen(context: viewModel.context))
} }
// MARK: - Private
private func showSuccessIndicator(title: String) {
parameters.userIndicatorController.submitIndicator(.init(id: .init(),
type: .modal(progress: .none, interactiveDismissDisabled: false, allowsInteraction: false),
title: title,
iconName: "checkmark",
persistent: false))
}
} }

View File

@ -10,7 +10,6 @@ import Foundation
enum SecureBackupRecoveryKeyScreenViewModelAction { enum SecureBackupRecoveryKeyScreenViewModelAction {
case done(mode: SecureBackupRecoveryKeyScreenViewMode) case done(mode: SecureBackupRecoveryKeyScreenViewMode)
case cancel case cancel
case resetEncryption
} }
enum SecureBackupRecoveryKeyScreenViewMode { enum SecureBackupRecoveryKeyScreenViewMode {
@ -82,7 +81,6 @@ enum SecureBackupRecoveryKeyScreenViewAction {
case copyKey case copyKey
case keySaved case keySaved
case confirmKey case confirmKey
case resetEncryption
case done case done
case cancel case cancel
} }

View File

@ -78,13 +78,11 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM
state.bindings.alertInfo = .init(id: .init(), state.bindings.alertInfo = .init(id: .init(),
title: L10n.screenRecoveryKeySetupConfirmationTitle, title: L10n.screenRecoveryKeySetupConfirmationTitle,
message: L10n.screenRecoveryKeySetupConfirmationDescription, message: L10n.screenRecoveryKeySetupConfirmationDescription,
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil), primaryButton: .init(title: L10n.actionContinue) { [weak self] in
secondaryButton: .init(title: L10n.actionContinue, action: { [weak self] in
guard let self else { return } guard let self else { return }
actionsSubject.send(.done(mode: context.viewState.mode)) actionsSubject.send(.done(mode: context.viewState.mode))
})) },
case .resetEncryption: secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
actionsSubject.send(.resetEncryption)
} }
} }

View File

@ -89,6 +89,7 @@ struct SecureBackupRecoveryKeyScreen: View {
} }
.buttonStyle(.compound(.primary)) .buttonStyle(.compound(.primary))
.disabled(context.confirmationRecoveryKey.isEmpty) .disabled(context.confirmationRecoveryKey.isEmpty)
.accessibilityIdentifier(A11yIdentifiers.secureBackupRecoveryKeyScreen.confirm)
} }
} }
@ -111,6 +112,7 @@ struct SecureBackupRecoveryKeyScreen: View {
} }
.buttonStyle(.compound(.primary)) .buttonStyle(.compound(.primary))
.disabled(context.viewState.recoveryKey == nil || context.viewState.doneButtonEnabled == false) .disabled(context.viewState.recoveryKey == nil || context.viewState.doneButtonEnabled == false)
.accessibilityIdentifier(A11yIdentifiers.secureBackupRecoveryKeyScreen.done)
} }
} }
@ -140,6 +142,7 @@ struct SecureBackupRecoveryKeyScreen: View {
} }
.font(.compound.bodyLGSemibold) .font(.compound.bodyLGSemibold)
.padding(.vertical, 11) .padding(.vertical, 11)
.accessibilityIdentifier(A11yIdentifiers.secureBackupRecoveryKeyScreen.generateRecoveryKey)
} else { } else {
HStack(spacing: 8) { HStack(spacing: 8) {
ProgressView() ProgressView()
@ -163,6 +166,7 @@ struct SecureBackupRecoveryKeyScreen: View {
} }
.tint(.compound.iconSecondary) .tint(.compound.iconSecondary)
.accessibilityLabel(L10n.actionCopy) .accessibilityLabel(L10n.actionCopy)
.accessibilityIdentifier(A11yIdentifiers.secureBackupRecoveryKeyScreen.copyRecoveryKey)
} }
} }
} }
@ -204,6 +208,7 @@ struct SecureBackupRecoveryKeyScreen: View {
.onSubmit { .onSubmit {
context.send(viewAction: .confirmKey) context.send(viewAction: .confirmKey)
} }
.accessibilityIdentifier(A11yIdentifiers.secureBackupRecoveryKeyScreen.recoveryKeyField)
if let subtitle = context.viewState.recoveryKeySubtitle { if let subtitle = context.viewState.recoveryKeySubtitle {
Text(subtitle) Text(subtitle)

View File

@ -11,12 +11,12 @@ import SwiftUI
struct SecureBackupScreenCoordinatorParameters { struct SecureBackupScreenCoordinatorParameters {
let appSettings: AppSettings let appSettings: AppSettings
let clientProxy: ClientProxyProtocol let clientProxy: ClientProxyProtocol
weak var navigationStackCoordinator: NavigationStackCoordinator?
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
} }
enum SecureBackupScreenCoordinatorAction { enum SecureBackupScreenCoordinatorAction {
case requestOIDCAuthorisation(URL) case manageRecoveryKey
case disableKeyBackup
} }
final class SecureBackupScreenCoordinator: CoordinatorProtocol { final class SecureBackupScreenCoordinator: CoordinatorProtocol {
@ -43,53 +43,10 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol {
guard let self else { return } guard let self else { return }
switch action { switch action {
case .recoveryKey: case .manageRecoveryKey:
let recoveryNavigationStackCoordinator = NavigationStackCoordinator() actionsSubject.send(.manageRecoveryKey)
case .disableKeyBackup:
let recoveryKeyCoordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: .init(secureBackupController: parameters.clientProxy.secureBackupController, actionsSubject.send(.disableKeyBackup)
userIndicatorController: parameters.userIndicatorController,
isModallyPresented: true))
recoveryKeyCoordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .cancel:
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
case .recoverySetUp:
showSuccessIndicator(title: L10n.screenRecoveryKeySetupSuccess)
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
case .recoveryChanged:
showSuccessIndicator(title: L10n.screenRecoveryKeyChangeSuccess)
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
case .recoveryFixed:
showSuccessIndicator(title: L10n.screenRecoveryKeyConfirmSuccess)
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
case .resetEncryption:
showEncryptionReset(recoveryNavigationStackCoordinator: recoveryNavigationStackCoordinator)
}
}
.store(in: &cancellables)
recoveryNavigationStackCoordinator.setRootCoordinator(recoveryKeyCoordinator, animated: true)
parameters.navigationStackCoordinator?.setSheetCoordinator(recoveryNavigationStackCoordinator)
case .keyBackup:
let navigationStackCoordinator = NavigationStackCoordinator()
let keyBackupCoordinator = SecureBackupKeyBackupScreenCoordinator(parameters: .init(secureBackupController: parameters.clientProxy.secureBackupController,
userIndicatorController: parameters.userIndicatorController))
keyBackupCoordinator.actions.sink { [weak self] action in
switch action {
case .done:
self?.parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
navigationStackCoordinator.setRootCoordinator(keyBackupCoordinator, animated: true)
parameters.navigationStackCoordinator?.setSheetCoordinator(navigationStackCoordinator)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@ -98,41 +55,4 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView { func toPresentable() -> AnyView {
AnyView(SecureBackupScreen(context: viewModel.context)) AnyView(SecureBackupScreen(context: viewModel.context))
} }
// MARK: - Private
private func showSuccessIndicator(title: String) {
parameters.userIndicatorController.submitIndicator(.init(id: .init(),
type: .modal(progress: .none, interactiveDismissDisabled: false, allowsInteraction: false),
title: title,
iconName: "checkmark",
persistent: false))
}
private func showEncryptionReset(recoveryNavigationStackCoordinator: NavigationStackCoordinator) {
let resetNavigationStackCoordinator = NavigationStackCoordinator()
let coordinator = EncryptionResetScreenCoordinator(parameters: .init(clientProxy: parameters.clientProxy,
navigationStackCoordinator: resetNavigationStackCoordinator,
userIndicatorController: parameters.userIndicatorController))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .cancel:
recoveryNavigationStackCoordinator.setSheetCoordinator(nil)
case .requestOIDCAuthorisation(let url):
actionsSubject.send(.requestOIDCAuthorisation(url))
case .resetFinished:
parameters.navigationStackCoordinator?.setSheetCoordinator(nil) // Dismiss the recovery screen
recoveryNavigationStackCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
resetNavigationStackCoordinator.setRootCoordinator(coordinator)
recoveryNavigationStackCoordinator.setSheetCoordinator(resetNavigationStackCoordinator)
}
} }

View File

@ -8,8 +8,8 @@
import Foundation import Foundation
enum SecureBackupScreenViewModelAction { enum SecureBackupScreenViewModelAction {
case recoveryKey case manageRecoveryKey
case keyBackup case disableKeyBackup
} }
struct SecureBackupScreenViewState: BindableState { struct SecureBackupScreenViewState: BindableState {

View File

@ -48,7 +48,7 @@ class SecureBackupScreenViewModel: SecureBackupScreenViewModelType, SecureBackup
override func process(viewAction: SecureBackupScreenViewAction) { override func process(viewAction: SecureBackupScreenViewAction) {
switch viewAction { switch viewAction {
case .recoveryKey: case .recoveryKey:
actionsSubject.send(.recoveryKey) actionsSubject.send(.manageRecoveryKey)
case .keyStorageToggled(let enable): case .keyStorageToggled(let enable):
let keyBackupState = secureBackupController.keyBackupState.value let keyBackupState = secureBackupController.keyBackupState.value
switch (keyBackupState, enable) { switch (keyBackupState, enable) {
@ -57,7 +57,7 @@ class SecureBackupScreenViewModel: SecureBackupScreenViewModelType, SecureBackup
enableBackup() enableBackup()
case (.enabled, false): case (.enabled, false):
state.bindings.keyStorageEnabled = keyBackupState.keyStorageToggleState // Reset the toggle in case the user cancels state.bindings.keyStorageEnabled = keyBackupState.keyStorageToggleState // Reset the toggle in case the user cancels
actionsSubject.send(.keyBackup) actionsSubject.send(.disableKeyBackup)
default: default:
break break
} }

View File

@ -53,7 +53,13 @@ struct SecureBackupScreen: View {
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)
}) })
keyStorageToggle ListRow(label: .plain(title: L10n.screenChatBackupKeyStorageToggleTitle,
description: context.viewState.keyStorageToggleDescription),
kind: .toggle($context.keyStorageEnabled))
.onChange(of: context.keyStorageEnabled) { _, newValue in
context.send(viewAction: .keyStorageToggled(newValue))
}
.accessibilityIdentifier(A11yIdentifiers.secureBackupScreen.keyStorage)
} }
} }
@ -67,15 +73,6 @@ struct SecureBackupScreen: View {
return description return description
} }
private var keyStorageToggle: some View {
ListRow(label: .plain(title: L10n.screenChatBackupKeyStorageToggleTitle,
description: context.viewState.keyStorageToggleDescription),
kind: .toggle($context.keyStorageEnabled))
.onChange(of: context.keyStorageEnabled) { _, newValue in
context.send(viewAction: .keyStorageToggled(newValue))
}
}
private var recoveryKeySection: some View { private var recoveryKeySection: some View {
Section { Section {
switch context.viewState.recoveryState { switch context.viewState.recoveryState {
@ -85,6 +82,7 @@ struct SecureBackupScreen: View {
icon: \.key, icon: \.key,
iconAlignment: .top), iconAlignment: .top),
kind: .navigationLink { context.send(viewAction: .recoveryKey) }) kind: .navigationLink { context.send(viewAction: .recoveryKey) })
.accessibilityIdentifier(A11yIdentifiers.secureBackupScreen.recoveryKey)
case .disabled: case .disabled:
ListRow(label: .default(title: L10n.screenChatBackupRecoveryActionSetup, ListRow(label: .default(title: L10n.screenChatBackupRecoveryActionSetup,
description: L10n.screenChatBackupRecoveryActionChangeDescription, description: L10n.screenChatBackupRecoveryActionChangeDescription,
@ -92,10 +90,12 @@ struct SecureBackupScreen: View {
iconAlignment: .top), iconAlignment: .top),
details: .icon(BadgeView(size: 10)), details: .icon(BadgeView(size: 10)),
kind: .navigationLink { context.send(viewAction: .recoveryKey) }) kind: .navigationLink { context.send(viewAction: .recoveryKey) })
.accessibilityIdentifier(A11yIdentifiers.secureBackupScreen.recoveryKey)
case .incomplete: case .incomplete:
ListRow(label: .plain(title: L10n.screenChatBackupRecoveryActionConfirm), ListRow(label: .plain(title: L10n.screenChatBackupRecoveryActionConfirm),
details: .icon(BadgeView(size: 10)), details: .icon(BadgeView(size: 10)),
kind: .navigationLink { context.send(viewAction: .recoveryKey) }) kind: .navigationLink { context.send(viewAction: .recoveryKey) })
.accessibilityIdentifier(A11yIdentifiers.secureBackupScreen.recoveryKey)
default: default:
ListRow(label: .plain(title: L10n.commonLoading), details: .isWaiting(true), kind: .label) ListRow(label: .plain(title: L10n.commonLoading), details: .isWaiting(true), kind: .label)
} }

View File

@ -630,6 +630,40 @@ class MockScreen: Identifiable {
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new)) let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .encryptionSettings, .encryptionSettingsOutOfSync:
let recoveryState: SecureBackupRecoveryState = id == .encryptionSettings ? .enabled : .incomplete
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", recoveryState: recoveryState))
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let navigationStackCoordinator = NavigationStackCoordinator()
navigationStackCoordinator.setRootCoordinator(BlankFormCoordinator())
let coordinator = EncryptionSettingsFlowCoordinator(parameters: .init(userSession: userSession,
appSettings: ServiceLocator.shared.settings,
userIndicatorController: UserIndicatorControllerMock(),
navigationStackCoordinator: navigationStackCoordinator))
retainedState.append(coordinator)
coordinator.start()
return navigationStackCoordinator
case .encryptionReset:
let recoveryState: SecureBackupRecoveryState = id == .encryptionSettings ? .enabled : .incomplete
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", recoveryState: recoveryState))
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
let userIndicatorController = UserIndicatorController()
userIndicatorController.window = windowManager.overlayWindow
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = EncryptionResetFlowCoordinator(parameters: .init(userSession: userSession,
userIndicatorController: userIndicatorController,
navigationStackCoordinator: navigationStackCoordinator,
windowManger: windowManager))
retainedState.append(coordinator)
coordinator.start()
return navigationStackCoordinator return navigationStackCoordinator
case .autoUpdatingTimeline: case .autoUpdatingTimeline:
let appSettings: AppSettings = ServiceLocator.shared.settings let appSettings: AppSettings = ServiceLocator.shared.settings

View File

@ -20,6 +20,9 @@ enum UITestsScreenIdentifier: String {
case createPoll case createPoll
case createRoom case createRoom
case createRoomNoUsers case createRoomNoUsers
case encryptionSettings
case encryptionSettingsOutOfSync
case encryptionReset
case roomLayoutBottom case roomLayoutBottom
case roomLayoutMiddle case roomLayoutMiddle
case roomLayoutTop case roomLayoutTop

View File

@ -0,0 +1,37 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import XCTest
@MainActor
class EncryptionResetUITests: XCTestCase {
var app: XCUIApplication!
@MainActor enum Step {
static let resetScreen = 0
static let passwordScreen = 1
static let resetingEncryption = 2
}
func testPasswordFlow() async throws {
app = Application.launch(.encryptionReset)
// Starting with the root screen.
try await app.assertScreenshot(.encryptionReset, step: Step.resetScreen)
// Confirm the intent to reset.
app.buttons[A11yIdentifiers.encryptionResetScreen.continueReset].tap()
app.buttons[A11yIdentifiers.alertInfo.primaryButton].tap()
try await app.assertScreenshot(.encryptionReset, step: Step.passwordScreen)
// Enter the password and submit.
let passwordField = app.secureTextFields[A11yIdentifiers.encryptionResetPasswordScreen.passwordField]
passwordField.clearAndTypeText("supersecurepassword", app: app)
app.buttons[A11yIdentifiers.encryptionResetPasswordScreen.submit].tap()
try await app.assertScreenshot(.encryptionReset, step: Step.resetingEncryption)
}
}

View File

@ -0,0 +1,80 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import XCTest
@MainActor
class EncryptionSettingsUITests: XCTestCase {
var app: XCUIApplication!
@MainActor enum Step {
static let secureBackupScreenSetUp = 0
static let keyBackupScreen = 1
static let secureBackupScreenDisabled = 2
static let setUpRecovery = 3
static let changeRecovery = 4
static let secureBackupScreenOutOfSync = 5
static let confirmRecovery = 6
}
func testFlow() async throws {
app = Application.launch(.encryptionSettings)
// Starting with key storage and recovery enabled.
try await app.assertScreenshot(.encryptionSettings, step: Step.secureBackupScreenSetUp)
// Toggle key storage off.
app.switches[A11yIdentifiers.secureBackupScreen.keyStorage].tap()
try await app.assertScreenshot(.encryptionSettings, step: Step.keyBackupScreen)
// Confirm deletion of keys.
app.buttons[A11yIdentifiers.secureBackupKeyBackupScreen.deleteKeyStorage].tap()
app.buttons[A11yIdentifiers.alertInfo.primaryButton].tap()
try await app.assertScreenshot(.encryptionSettings, step: Step.secureBackupScreenDisabled)
// Toggle key storage back on and set up recovery.
app.switches[A11yIdentifiers.secureBackupScreen.keyStorage].tap()
app.buttons[A11yIdentifiers.secureBackupScreen.recoveryKey].tap()
try await app.assertScreenshot(.encryptionSettings, step: Step.setUpRecovery)
// Generate and copy a new recovery key.
app.buttons[A11yIdentifiers.secureBackupRecoveryKeyScreen.generateRecoveryKey].tap()
app.buttons[A11yIdentifiers.secureBackupRecoveryKeyScreen.copyRecoveryKey].tap()
app.buttons[A11yIdentifiers.secureBackupRecoveryKeyScreen.done].tap()
app.buttons[A11yIdentifiers.alertInfo.primaryButton].tap()
try await app.assertScreenshot(.encryptionSettings, step: Step.secureBackupScreenSetUp)
// Change the recovery key.
app.buttons[A11yIdentifiers.secureBackupScreen.recoveryKey].tap()
try await app.assertScreenshot(.encryptionSettings, step: Step.changeRecovery)
// Generate and copy the updated recovery key.
app.buttons[A11yIdentifiers.secureBackupRecoveryKeyScreen.generateRecoveryKey].tap()
app.buttons[A11yIdentifiers.secureBackupRecoveryKeyScreen.copyRecoveryKey].tap()
app.buttons[A11yIdentifiers.secureBackupRecoveryKeyScreen.done].tap()
app.buttons[A11yIdentifiers.alertInfo.primaryButton].tap()
try await app.assertScreenshot(.encryptionSettings, step: Step.secureBackupScreenSetUp)
}
func testOutOfSyncFlow() async throws {
app = Application.launch(.encryptionSettingsOutOfSync)
// Starting with key storage and recovery enabled.
try await app.assertScreenshot(.encryptionSettings, step: Step.secureBackupScreenOutOfSync)
// Confirm the recovery key.
app.buttons[A11yIdentifiers.secureBackupScreen.recoveryKey].tap()
try await app.assertScreenshot(.encryptionSettings, step: Step.confirmRecovery)
// Enter the recovery key and submit.
let recoveryKeyField = app.secureTextFields[A11yIdentifiers.secureBackupRecoveryKeyScreen.recoveryKeyField]
recoveryKeyField.clearAndTypeText("sUpe RSec rEtR Ecov ERYk Ey12", app: app)
app.buttons[A11yIdentifiers.secureBackupRecoveryKeyScreen.confirm].tap()
try await app.assertScreenshot(.encryptionSettings, step: Step.secureBackupScreenSetUp)
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.