Encryption Flow Coordinators. (#3471)

* Manage the secure backup screens with flow coordinators.

* Add UI tests for the EncryptionSettingsFlowCoordinator.

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

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

View File

@ -158,6 +158,7 @@
21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD558A898847C179E4B7A237 /* SecureBackupKeyBackupScreen.swift */; };
22882C710BC99EC34A5024A0 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.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 */; };
23701DE32ACD6FD40AA992C3 /* MediaUploadingPreprocessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE203026B9AD3DB412439866 /* MediaUploadingPreprocessorTests.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 */; };
2CA61BB208CD82EBDB58CD13 /* VideoRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */; };
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 */; };
2DA27D78560D5F79B917E163 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44E35AA87F49503E7B3BF6E /* AudioConverter.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 */; };
4D0F4385B7DDB68C66C78857 /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C258C9C815272911A5B132C3 /* FormattedBodyText.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 */; };
4DAEE2468669848B6C9F55B4 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33035418BB35754232985871 /* TimelineReadReceiptsView.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 */; };
64E541F88F35BD126C4AFCA1 /* AppLockScreenPINKeypad.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38345442415E07A931197C55 /* AppLockScreenPINKeypad.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 */; };
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1423AB065857FA546444DB15 /* NotificationManager.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 */; };
C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.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 */; };
CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; };
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 */; };
FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */; };
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 */
/* Begin PBXContainerItemProxy section */
@ -1475,6 +1481,7 @@
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>"; };
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>"; };
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>"; };
@ -1553,6 +1560,7 @@
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>"; };
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>"; };
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>"; };
@ -1632,6 +1640,7 @@
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>"; };
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>"; };
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>"; };
@ -1906,6 +1915,7 @@
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>"; };
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>"; };
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>"; };
@ -2035,6 +2045,7 @@
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>"; };
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>"; };
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>"; };
@ -2244,6 +2255,7 @@
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>"; };
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>"; };
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>"; };
@ -2935,6 +2947,7 @@
FC83F47D2173B7538AA72E0E /* RoomSummaryProviderMock.swift */,
D479DF730528153665E5782E /* RoomTimelineControllerFactoryMock.swift */,
F74532E01B317C56C1BE8FA8 /* RoomTimelineProviderMock.swift */,
4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */,
248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */,
7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */,
AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */,
@ -3531,6 +3544,8 @@
0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */,
A9B069D7772DDF6513E0F1B8 /* AuthenticationFlowCoordinator.swift */,
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */,
A07B011547B201A836C03052 /* EncryptionResetFlowCoordinator.swift */,
ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */,
C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */,
A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */,
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
@ -4444,6 +4459,8 @@
295E28C3B9EAADF519BF2F44 /* AuthenticationFlowCoordinatorUITests.swift */,
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */,
F8CEB4634C0DD7779C4AB504 /* CreateRoomScreenUITests.swift */,
BE4C76F31A382B8E4DD07583 /* EncryptionResetUITests.swift */,
3BF8E5D4C95974B96A18C80E /* EncryptionSettingsUITests.swift */,
3368395F06AA180138E185B6 /* PollFormScreenUITests.swift */,
C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */,
45571C2EBD98ED7E0CEA7AF7 /* RoomRolesAndPermissionsUITests.swift */,
@ -5287,6 +5304,7 @@
isa = PBXGroup;
children = (
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */,
5EFB1D29B0870AFB6A56E9B8 /* IdentityResetHandleSDKMock.swift */,
E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */,
);
path = SDK;
@ -6470,6 +6488,7 @@
03CDCA6243F89B194E3FAD17 /* EncryptionAuthenticity.swift in Sources */,
FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */,
46A6DB0F78FB399BD59E2D41 /* EncryptionKeyProviderProtocol.swift in Sources */,
64F8590F4BEE4DA231F97D83 /* EncryptionResetFlowCoordinator.swift in Sources */,
0C6DF318E9C8F6461E6ABDE7 /* EncryptionResetPasswordScreen.swift in Sources */,
36926D795D6D19177C7812F8 /* EncryptionResetPasswordScreenCoordinator.swift in Sources */,
B1B255CE0E4306DD6E09D936 /* EncryptionResetPasswordScreenModels.swift in Sources */,
@ -6480,6 +6499,7 @@
97BAEDD9054FB5F233EE928B /* EncryptionResetScreenModels.swift in Sources */,
EC3320639828BED8B3E5F2C6 /* EncryptionResetScreenViewModel.swift in Sources */,
A0868BDE84D2140A885BE3C9 /* EncryptionResetScreenViewModelProtocol.swift in Sources */,
4D2B54233C7B2C04B4ABE55A /* EncryptionSettingsFlowCoordinator.swift in Sources */,
50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */,
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */,
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */,
@ -6527,6 +6547,7 @@
D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */,
01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */,
AADE7C2497A7B55D8BED7BD6 /* IdentityConfirmedScreenViewModelProtocol.swift in Sources */,
FFD52DCDA6962055A363CC8F /* IdentityResetHandleSDKMock.swift in Sources */,
BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */,
7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */,
B796A25F282C0A340D1B9C12 /* ImageRoomTimelineItemContent.swift in Sources */,
@ -6868,6 +6889,7 @@
67160204A8D362BB7D4AD259 /* Search.swift in Sources */,
339BC18777912E1989F2F17D /* Section.swift in Sources */,
F833D5B5BE6707F961FA88DB /* SecureBackupController.swift in Sources */,
2D0E3983288E2D35613AD681 /* SecureBackupControllerMock.swift in Sources */,
0C88044649BAEE6C49BFC43A /* SecureBackupControllerProtocol.swift in Sources */,
21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */,
5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */,
@ -7092,6 +7114,8 @@
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */,
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */,
9F19096BFA629C0AC282B1E4 /* CreateRoomScreenUITests.swift in Sources */,
CACD1352927336F01FC76612 /* EncryptionResetUITests.swift in Sources */,
230981086F0199F913434D6B /* EncryptionSettingsUITests.swift in Sources */,
0CF81807BE5FBFC9E2BBCECF /* PollFormScreenUITests.swift in Sources */,
44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.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
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()
var actions: AnyPublisher<OnboardingFlowCoordinatorAction, Never> {
@ -251,7 +253,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
appSettings.hasRunIdentityConfirmationOnboarding = true
stateMachine.tryEvent(.nextSkippingIdentityConfimed)
case .reset:
presentEncryptionResetScreen()
startEncryptionResetFlow()
case .logout:
actionsSubject.send(.logout)
}
@ -295,12 +297,8 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }
switch action {
case .recoveryFixed:
case .complete:
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)
@ -308,31 +306,31 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
presentCoordinator(coordinator)
}
private func presentEncryptionResetScreen() {
private func startEncryptionResetFlow() {
let resetNavigationStackCoordinator = NavigationStackCoordinator()
let coordinator = EncryptionResetScreenCoordinator(parameters: .init(clientProxy: userSession.clientProxy,
navigationStackCoordinator: resetNavigationStackCoordinator,
userIndicatorController: userIndicatorController))
let coordinator = EncryptionResetFlowCoordinator(parameters: .init(userSession: userSession,
userIndicatorController: userIndicatorController,
navigationStackCoordinator: resetNavigationStackCoordinator,
windowManger: windowManager))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .cancel:
navigationStackCoordinator.setSheetCoordinator(nil)
case .requestOIDCAuthorisation(let url):
presentOIDCAuthorisationScreen(url: url)
case .resetFinished:
case .resetComplete:
// Moving to next state is handled by the global session verification listener
navigationStackCoordinator.setSheetCoordinator(nil)
case .cancel:
navigationStackCoordinator.setSheetCoordinator(nil)
}
}
.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() {
@ -411,12 +409,4 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
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
private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator?
// periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
// periphery:ignore - retaining purpose
private var encryptionSettingsFlowCoordinator: EncryptionSettingsFlowCoordinator?
private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init()
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
// on the same loop run
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
self.presentSecureBackupScreen(animated: animated)
self.startEncryptionSettingsFlow(animated: animated)
}
default:
break
@ -102,7 +103,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
self.actionsSubject.send(.runLogoutFlow)
}
case .secureBackup:
presentSecureBackupScreen(animated: true)
startEncryptionSettingsFlow(animated: true)
case .userDetails:
presentUserDetailsEditScreen()
case let .manageAccount(url):
@ -145,21 +146,22 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
actionsSubject.send(.presentedSettings)
}
private func presentSecureBackupScreen(animated: Bool) {
let coordinator = SecureBackupScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
clientProxy: parameters.userSession.clientProxy,
navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: parameters.userIndicatorController))
coordinator.actions.sink { [weak self] action in
private func startEncryptionSettingsFlow(animated: Bool) {
let coordinator = EncryptionSettingsFlowCoordinator(parameters: .init(userSession: parameters.userSession,
appSettings: parameters.appSettings,
userIndicatorController: parameters.userIndicatorController,
navigationStackCoordinator: navigationStackCoordinator))
coordinator.actionsPublisher.sink { [weak self] action in
switch action {
case .requestOIDCAuthorisation(let url):
self?.presentAccountManagementURL(url)
case .complete:
// The flow coordinator tidies up the stack, no need to do anything.
self?.encryptionSettingsFlowCoordinator = nil
}
}
.store(in: &cancellables)
navigationStackCoordinator.push(coordinator, animated: animated)
encryptionSettingsFlowCoordinator = coordinator
coordinator.start()
}
private func presentUserDetailsEditScreen() {

View File

@ -13,6 +13,8 @@ struct ClientProxyMockConfiguration {
var deviceID: String?
var roomSummaryProvider: RoomSummaryProviderProtocol? = RoomSummaryProviderMock(.init())
var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol?
var recoveryState: SecureBackupRecoveryState = .enabled
}
enum ClientProxyMockError: Error {
@ -78,12 +80,8 @@ extension ClientProxyMock {
loadMediaThumbnailForSourceWidthHeightThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic)
loadMediaFileForSourceFilenameThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic)
secureBackupController = {
let secureBackupController = SecureBackupControllerMock()
secureBackupController.underlyingRecoveryState = .init(CurrentValueSubject<SecureBackupRecoveryState, Never>(.enabled))
secureBackupController.underlyingKeyBackupState = .init(CurrentValueSubject<SecureBackupKeyBackupState, Never>(.enabled))
return secureBackupController
}()
secureBackupController = SecureBackupControllerMock(.init(recoveryState: configuration.recoveryState))
resetIdentityReturnValue = .success(IdentityResetHandleSDKMock(.init()))
roomForIdentifierClosure = { [weak self] identifier in
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 bugReportScreen = BugReportScreen()
static let changeServerScreen = ChangeServer()
static let encryptionResetScreen = EncryptionResetScreen()
static let encryptionResetPasswordScreen = EncryptionResetPasswordScreen()
static let homeScreen = HomeScreen()
static let loginScreen = LoginScreen()
static let authenticationStartScreen = AuthenticationStartScreen()
@ -24,6 +26,9 @@ enum A11yIdentifiers {
static let roomDetailsScreen = RoomDetailsScreen()
static let roomNotificationSettingsScreen = RoomNotificationSettingsScreen()
static let roomRolesAndPermissionsScreen = RoomRolesAndPermissionsScreen()
static let secureBackupScreen = SecureBackupScreen()
static let secureBackupKeyBackupScreen = SecureBackupKeyBackupScreen()
static let secureBackupRecoveryKeyScreen = SecureBackupRecoveryKeyScreen()
static let serverConfirmationScreen = ServerConfirmationScreen()
static let sessionVerificationScreen = SessionVerificationScreen()
static let settingsScreen = SettingsScreen()
@ -81,6 +86,15 @@ enum A11yIdentifiers {
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 {
let userAvatar = "home_screen-user_avatar"
let recoveryKeyConfirmationBannerContinue = "home_screen-recovery_key_confirmation_continue"
@ -178,6 +192,23 @@ enum A11yIdentifiers {
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 {
let `continue` = "server_confirmation-continue"
let changeServer = "server_confirmation-change_server"

View File

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

View File

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

View File

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

View File

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

View File

@ -9,14 +9,14 @@ import Combine
import SwiftUI
enum EncryptionResetScreenCoordinatorAction {
case cancel
case requestOIDCAuthorisation(URL)
case requestPassword(passwordPublisher: PassthroughSubject<String, Never>)
case resetFinished
case cancel
}
struct EncryptionResetScreenCoordinatorParameters {
let clientProxy: ClientProxyProtocol
let navigationStackCoordinator: NavigationStackCoordinator
let userIndicatorController: UserIndicatorControllerProtocol
}
@ -43,10 +43,10 @@ final class EncryptionResetScreenCoordinator: CoordinatorProtocol {
guard let self else { return }
switch action {
case .requestPassword:
presentPasswordScreen()
case .requestOIDCAuthorisation(let url):
self.actionsSubject.send(.requestOIDCAuthorisation(url))
case .requestPassword(let passwordPublisher):
self.actionsSubject.send(.requestPassword(passwordPublisher: passwordPublisher))
case .resetFinished:
self.actionsSubject.send(.resetFinished)
case .cancel:
@ -63,23 +63,4 @@ final class EncryptionResetScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView {
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.
//
import Combine
import Foundation
enum EncryptionResetScreenViewModelAction {
case requestPassword
case requestPassword(passwordPublisher: PassthroughSubject<String, Never>)
case requestOIDCAuthorisation(url: URL)
case resetFinished
case cancel

View File

@ -21,6 +21,7 @@ class EncryptionResetScreenViewModel: EncryptionResetScreenViewModelType, Encryp
}
private var identityResetHandle: IdentityResetHandle?
private var passwordCancellable: AnyCancellable?
init(clientProxy: ClientProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) {
self.clientProxy = clientProxy
@ -46,12 +47,6 @@ class EncryptionResetScreenViewModel: EncryptionResetScreenViewModelType, Encryp
}
}
func continueResetFlowWith(password: String) {
Task {
await resetWith(password: password)
}
}
func stop() {
Task {
await identityResetHandle?.cancel()
@ -80,7 +75,14 @@ class EncryptionResetScreenViewModel: EncryptionResetScreenViewModelType, Encryp
switch handle.authType() {
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):
guard let url = URL(string: oidcInfo.approvalUrl) else {
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 context: EncryptionResetScreenViewModelType.Context { get }
func continueResetFlowWith(password: String)
func stop()
}

View File

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

View File

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

View File

@ -15,23 +15,22 @@ struct SecureBackupRecoveryKeyScreenCoordinatorParameters {
}
enum SecureBackupRecoveryKeyScreenCoordinatorAction {
case cancel
case recoverySetUp
case recoveryChanged
case recoveryFixed
case resetEncryption
case complete
}
final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
private let parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters
private var viewModel: SecureBackupRecoveryKeyScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> = .init()
var actions: AnyPublisher<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = SecureBackupRecoveryKeyScreenViewModel(secureBackupController: parameters.secureBackupController,
userIndicatorController: parameters.userIndicatorController,
isModallyPresented: parameters.isModallyPresented)
@ -44,20 +43,19 @@ final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
guard let self else { return }
switch action {
case .cancel:
self.actionsSubject.send(.cancel)
self.actionsSubject.send(.complete)
case .done(let mode):
switch mode {
case .setupRecovery:
self.actionsSubject.send(.recoverySetUp)
showSuccessIndicator(title: L10n.screenRecoveryKeySetupSuccess)
case .changeRecovery:
self.actionsSubject.send(.recoveryChanged)
showSuccessIndicator(title: L10n.screenRecoveryKeyChangeSuccess)
case .fixRecovery:
self.actionsSubject.send(.recoveryFixed)
showSuccessIndicator(title: L10n.screenRecoveryKeyConfirmSuccess)
case .unknown:
fatalError()
}
case .resetEncryption:
self.actionsSubject.send(.resetEncryption)
self.actionsSubject.send(.complete)
}
}
.store(in: &cancellables)
@ -66,4 +64,14 @@ final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView {
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 {
case done(mode: SecureBackupRecoveryKeyScreenViewMode)
case cancel
case resetEncryption
}
enum SecureBackupRecoveryKeyScreenViewMode {
@ -82,7 +81,6 @@ enum SecureBackupRecoveryKeyScreenViewAction {
case copyKey
case keySaved
case confirmKey
case resetEncryption
case done
case cancel
}

View File

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

View File

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

View File

@ -11,12 +11,12 @@ import SwiftUI
struct SecureBackupScreenCoordinatorParameters {
let appSettings: AppSettings
let clientProxy: ClientProxyProtocol
weak var navigationStackCoordinator: NavigationStackCoordinator?
let userIndicatorController: UserIndicatorControllerProtocol
}
enum SecureBackupScreenCoordinatorAction {
case requestOIDCAuthorisation(URL)
case manageRecoveryKey
case disableKeyBackup
}
final class SecureBackupScreenCoordinator: CoordinatorProtocol {
@ -43,53 +43,10 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol {
guard let self else { return }
switch action {
case .recoveryKey:
let recoveryNavigationStackCoordinator = NavigationStackCoordinator()
let recoveryKeyCoordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: .init(secureBackupController: parameters.clientProxy.secureBackupController,
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)
case .manageRecoveryKey:
actionsSubject.send(.manageRecoveryKey)
case .disableKeyBackup:
actionsSubject.send(.disableKeyBackup)
}
}
.store(in: &cancellables)
@ -98,41 +55,4 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView {
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
enum SecureBackupScreenViewModelAction {
case recoveryKey
case keyBackup
case manageRecoveryKey
case disableKeyBackup
}
struct SecureBackupScreenViewState: BindableState {

View File

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

View File

@ -53,7 +53,13 @@ struct SecureBackupScreen: View {
.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
}
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 {
Section {
switch context.viewState.recoveryState {
@ -85,6 +82,7 @@ struct SecureBackupScreen: View {
icon: \.key,
iconAlignment: .top),
kind: .navigationLink { context.send(viewAction: .recoveryKey) })
.accessibilityIdentifier(A11yIdentifiers.secureBackupScreen.recoveryKey)
case .disabled:
ListRow(label: .default(title: L10n.screenChatBackupRecoveryActionSetup,
description: L10n.screenChatBackupRecoveryActionChangeDescription,
@ -92,10 +90,12 @@ struct SecureBackupScreen: View {
iconAlignment: .top),
details: .icon(BadgeView(size: 10)),
kind: .navigationLink { context.send(viewAction: .recoveryKey) })
.accessibilityIdentifier(A11yIdentifiers.secureBackupScreen.recoveryKey)
case .incomplete:
ListRow(label: .plain(title: L10n.screenChatBackupRecoveryActionConfirm),
details: .icon(BadgeView(size: 10)),
kind: .navigationLink { context.send(viewAction: .recoveryKey) })
.accessibilityIdentifier(A11yIdentifiers.secureBackupScreen.recoveryKey)
default:
ListRow(label: .plain(title: L10n.commonLoading), details: .isWaiting(true), kind: .label)
}

View File

@ -630,6 +630,40 @@ class MockScreen: Identifiable {
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = PollFormScreenCoordinator(parameters: .init(mode: .new))
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
case .autoUpdatingTimeline:
let appSettings: AppSettings = ServiceLocator.shared.settings

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.