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 */; };
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)
}
}

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