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 835e30f0a1
commit 13c10b434b
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)
}
}

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:83ebdd64a97d8cef02516cb5be9d5191077c8b3be41252e828a4b83d45004b6e
size 133247

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e9dfc381ac69b5e415c4bdf05175ceb6df08cec2db54f3f4b02172eda1f6dd64
size 162470

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9720f0cd45534c9699eef06f8a2afd1504d5b51a8773b2b76585002b23d67b77
size 92574

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7170468705d17c9ba1c6062524d2ccc4cbbd54fd45f1056e53921b20fb6756f9
size 98637

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:26acd80deba0bdd9babf133b0b015b6f21942fa02983b921da28925a92573f8e
size 140798

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3618590fa6e84095976811f53bc283123654470c9aa15babfaec79cc16f19e88
size 177016

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:28d3ae3eb6b7ed45c41e1381c052a080906ff767aad74562e09b34b432ba1f06
size 119892

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9c7753ecb9a032c14ac1b53ec94dfaf055da89c3d053b0dd095b15b291129730
size 141912

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:46a5294fe4bf36583b6a7f58602e2ea00761e6027d74e9c982a99f912bf28c26
size 232922

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a6dc3f0216fa3281196281e997a5cceba862c8427ad352674b860b9d6c16ca02
size 157003

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3fbf90b3889e39f75abb39bd5e92ec60dfe18968c85b5ba9c56addf0c4579e6d
size 103188

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9f4752e15127fd336156f09cd998b96931b1a04f85b3ada3c1cd043d2461195
size 116493

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9690d135c26b3bef7c9e1ecd25fe5a45605bf1838f5f3d22f34ef4cc47cf59dc
size 211322

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3f5e7ccbc806ce6ef88e465be9bb879ef31fd3675822368c93126d66563e07b
size 121844

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1bfdba161a6e974df12efd09a48cd905d8162c828c2e6af99eaed74a8631891b
size 210352

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:db98e2b1dc6dc2c4f55aa46e783366c69e5ff4521cd0179be0028f47f0387688
size 119868

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59b95216f4ee18fa2b5c35fa237caf58ac7a976ef70c2a52c6a95ada9966c9c1
size 76515

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:745a30111d340511f2be87af6df4ce85ec00baa7d1b085b1082c922337c59ca2
size 72736

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca24039f2eacc6517cb7131cfba469fc3db152e7fa28e63307047b170344c17b
size 165666

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8b12eda65d7c9b472d156e7a964d6bf962810e881d5310bf04932a05a74a2443
size 100696