From 9a28682a35c2a3eb2b44688d9e52a4295bfbb849 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 5 Aug 2024 09:35:18 +0300 Subject: [PATCH] Crypto identity reset (#3107) * Add EncryptionReset password entry screen * Expose client proxy identity reset method * Update the IdentityConfirmationScreen to the latest designs * Replace the old recovery key reset screen with a new EncryptionReset one * Fixes #3102 - Add support for resetting the user's whole crypto identity * Address review comments * Bump the SDK to v1.0.30 --- ElementX.xcodeproj/project.pbxproj | 118 +++++++---- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../en.lproj/Localizable.strings | 18 +- .../OnboardingFlowCoordinator.swift | 41 +++- .../SettingsFlowCoordinator.swift | 10 +- .../UserSessionFlowCoordinator.swift | 1 + ElementX/Sources/Generated/Strings.swift | 32 +++ .../Mocks/Generated/GeneratedMocks.swift | 64 ++++++ .../Mocks/Generated/SDKGeneratedMocks.swift | 191 ++++++++++++++++++ .../SwiftUI/Views/RoundedLabelItem.swift | 5 +- ...yptionResetPasswordScreenCoordinator.swift | 68 +++++++ .../EncryptionResetPasswordScreenModels.swift | 41 ++++ ...cryptionResetPasswordScreenViewModel.swift | 42 ++++ ...esetPasswordScreenViewModelProtocol.swift} | 6 +- .../View/EncryptionResetPasswordScreen.swift | 83 ++++++++ .../EncryptionResetScreenCoordinator.swift | 90 +++++++++ .../EncryptionResetScreenModels.swift} | 16 +- .../EncryptionResetScreenViewModel.swift | 156 ++++++++++++++ ...cryptionResetScreenViewModelProtocol.swift | 25 +++ .../View/EncryptionResetScreen.swift | 119 +++++++++++ .../View/AnalyticsPromptScreen.swift | 12 +- .../View/IdentityConfirmationScreen.swift | 19 +- .../ResetRecoveryKeyScreenCoordinator.swift | 56 ----- .../ResetRecoveryKeyScreenViewModel.swift | 41 ---- .../View/ResetRecoveryKeyScreen.swift | 75 ------- ...reBackupRecoveryKeyScreenCoordinator.swift | 6 +- .../SecureBackupRecoveryKeyScreenModels.swift | 4 +- ...cureBackupRecoveryKeyScreenViewModel.swift | 31 ++- .../View/SecureBackupRecoveryKeyScreen.swift | 5 +- .../SecureBackupScreenCoordinator.swift | 53 +++-- .../SettingsScreenCoordinator.swift | 4 +- .../Sources/Services/Client/ClientProxy.swift | 10 +- .../Services/Client/ClientProxyProtocol.swift | 4 +- ...est_analyticsPromptScreen-iPad-en-GB.1.png | 4 +- ...st_analyticsPromptScreen-iPad-pseudo.1.png | 4 +- ...nalyticsPromptScreen-iPhone-15-en-GB.1.png | 4 +- ...alyticsPromptScreen-iPhone-15-pseudo.1.png | 4 +- ...PromptScreenCheckmarkItem-iPad-en-GB.1.png | 4 +- ...romptScreenCheckmarkItem-iPad-pseudo.1.png | 4 +- ...tScreenCheckmarkItem-iPhone-15-en-GB.1.png | 4 +- ...ScreenCheckmarkItem-iPhone-15-pseudo.1.png | 4 +- ...yptionResetPasswordScreen-iPad-en-GB.1.png | 3 + ...ptionResetPasswordScreen-iPad-pseudo.1.png | 3 + ...nResetPasswordScreen-iPhone-15-en-GB.1.png | 3 + ...ResetPasswordScreen-iPhone-15-pseudo.1.png | 3 + ...est_encryptionResetScreen-iPad-en-GB.1.png | 3 + ...st_encryptionResetScreen-iPad-pseudo.1.png | 3 + ...ncryptionResetScreen-iPhone-15-en-GB.1.png | 3 + ...cryptionResetScreen-iPhone-15-pseudo.1.png | 3 + ...dentityConfirmationScreen-iPad-en-GB.1.png | 4 +- ...entityConfirmationScreen-iPad-pseudo.1.png | 4 +- ...tyConfirmationScreen-iPhone-15-en-GB.1.png | 4 +- ...yConfirmationScreen-iPhone-15-pseudo.1.png | 4 +- ...st_resetRecoveryKeyScreen-iPad-en-GB.1.png | 3 - ...t_resetRecoveryKeyScreen-iPad-pseudo.1.png | 3 - ...setRecoveryKeyScreen-iPhone-15-en-GB.1.png | 3 - ...etRecoveryKeyScreen-iPhone-15-pseudo.1.png | 3 - project.yml | 2 +- 58 files changed, 1217 insertions(+), 319 deletions(-) create mode 100644 ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenCoordinator.swift create mode 100644 ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenModels.swift create mode 100644 ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenViewModel.swift rename ElementX/Sources/Screens/{SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenViewModelProtocol.swift => EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenViewModelProtocol.swift} (72%) create mode 100644 ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/View/EncryptionResetPasswordScreen.swift create mode 100644 ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenCoordinator.swift rename ElementX/Sources/Screens/{SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenModels.swift => EncryptionReset/EncryptionResetScreen/EncryptionResetScreenModels.swift} (79%) create mode 100644 ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/View/EncryptionResetScreen.swift delete mode 100644 ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenCoordinator.swift delete mode 100644 ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenViewModel.swift delete mode 100644 ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/View/ResetRecoveryKeyScreen.swift create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPad-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPad-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPhone-15-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPhone-15-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPad-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPad-pseudo.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPhone-15-en-GB.1.png create mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPhone-15-pseudo.1.png delete mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPad-en-GB.1.png delete mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPad-pseudo.1.png delete mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPhone-15-en-GB.1.png delete mode 100644 PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPhone-15-pseudo.1.png diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 4935e3d18..4705d05ca 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ 0C346A4AD174F441EDB1414E /* IdentityConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB76A9AFC6CCAD4998D9B045 /* IdentityConfirmationScreenViewModel.swift */; }; 0C47AE2CA7929CB3B0E2D793 /* ServerSelectionScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */; }; 0C58A846F61949B1D545D661 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421E716C521F96D24ECE69B3 /* NoticeRoomTimelineItem.swift */; }; + 0C6DF318E9C8F6461E6ABDE7 /* EncryptionResetPasswordScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E1584F8BCF407BB94F48F04 /* EncryptionResetPasswordScreen.swift */; }; 0C797CD650DFD2876BEC5173 /* CollapsibleReactionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7C6DDBB5D12F6EF6A3D6E1 /* CollapsibleReactionLayout.swift */; }; 0C88044649BAEE6C49BFC43A /* SecureBackupControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C21A715237F2B6D6E80998C /* SecureBackupControllerProtocol.swift */; }; 0C932A5158C1D0604DFC5750 /* ComposerToolbarViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA29952595B804DA221A0C1D /* ComposerToolbarViewModelTests.swift */; }; @@ -203,6 +204,7 @@ 2F66701B15657A87B4AC3A0A /* WaitlistScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE2B7AD979BDEE09FEDB08 /* WaitlistScreenModels.swift */; }; 2F94054F50E312AF30BE07F3 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B21E611DADDEF00307E7AC /* String.swift */; }; 2FEC6652055984389CE1BBEC /* TimelineProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F03079F6B5EF9CA005F14 /* TimelineProxyProtocol.swift */; }; + 3041EBA2660F28FFB7BDA339 /* EncryptionResetScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */; }; 3042527CB344A9EF1157FC26 /* AudioRecorderStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */; }; 308BD9343B95657FAA583FB7 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; }; 30CC1DB7CE357659C82AA115 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; }; @@ -227,6 +229,7 @@ 35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; }; 366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.swift */; }; 368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; }; + 36926D795D6D19177C7812F8 /* EncryptionResetPasswordScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6935A55AB3B0C94BC566DD6 /* EncryptionResetPasswordScreenCoordinator.swift */; }; 369BF960E52BBEE61F8A5BD1 /* BlockedUsersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */; }; 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; }; 36AD4DD4C798E22584ED3200 /* SentrySwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 75361A9D8A3C5501EADB225D /* SentrySwiftUI */; }; @@ -257,7 +260,6 @@ 3C549A0BF39F8A854D45D9FD /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; }; 3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */; }; 3CE4C5071B6D2576E2473989 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B07B296D7A9D2F09120853 /* OrderedSet.swift */; }; - 3D2E3E1B01D4389DCBE8F0E8 /* ResetRecoveryKeyScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E995E3F726488F8938F0BCD1 /* ResetRecoveryKeyScreenCoordinator.swift */; }; 3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DC38E64A5ED3FDB201029A /* BugReportService.swift */; }; 3DAD62988F072607441CB7A5 /* PollFormScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */; }; 3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861FE915C7B5466E6962BBA /* StartChatScreen.swift */; }; @@ -439,7 +441,6 @@ 67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; }; 67D6E0700A9C1E676F6231F8 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = AD544C0FA48DFFB080920061 /* Collections */; }; 67E9926C4572C54F59FCA91A /* AuthenticationFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B069D7772DDF6513E0F1B8 /* AuthenticationFlowCoordinator.swift */; }; - 680062C402ECB8FCAAE85A5C /* ResetRecoveryKeyScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99637028A8BD2843A35A92D4 /* ResetRecoveryKeyScreenViewModelProtocol.swift */; }; 6817EAD73DC1FFD8B943B5B9 /* HomeScreenRoomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73587C2E3CF5998361AE516 /* HomeScreenRoomTests.swift */; }; 68184EF36396424FE19A727D /* MediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */; }; 6832733838C57A7D3FE8FEB5 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; }; @@ -654,6 +655,7 @@ 97189E495F0E47805D1868DB /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 527578916BD388A09F5A8036 /* DTCoreText */; }; 978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */; }; 97969EF0B9C412CD38E5CA93 /* AppLockScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4005D82E9D27BAF006A8FE1 /* AppLockScreenViewModel.swift */; }; + 97BAEDD9054FB5F233EE928B /* EncryptionResetScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306AB507E1027D6C5C147EB6 /* EncryptionResetScreenModels.swift */; }; 981853650217B6C8ECDD998C /* NavigationRootCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */; }; 983896D611ABF52A5C37498D /* RoomSummaryProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */; }; 9847B056C1A216C314D21E68 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A1AB5A84D843B6AC8D5F1E /* AuthenticationService.swift */; }; @@ -674,7 +676,6 @@ 9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F2402D738694F98729A441 /* RoomTimelineProvider.swift */; }; 9C4EC28A921486B1775D7F8C /* IdentityConfirmedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */; }; 9C55746D8F6A3E35CFCF4A7A /* AuthenticationStartLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 598F01EBD0C4CC550C644418 /* AuthenticationStartLogo.swift */; }; - 9C9838B68C00C980A498050C /* ResetRecoveryKeyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC30DEC0097B9D217493007 /* ResetRecoveryKeyScreen.swift */; }; 9D2E03DB175A6AB14589076D /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; }; 9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; }; 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; }; @@ -690,6 +691,8 @@ 9FB41B0E8B2AA9B404E52C8B /* AppLockSetupBiometricsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */; }; A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */; }; A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */; }; + A0601810597769B81C2358AF /* EncryptionResetPasswordScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */; }; + A0868BDE84D2140A885BE3C9 /* EncryptionResetScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8562F4D7DE073BC32902AB /* EncryptionResetScreenViewModelProtocol.swift */; }; A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */; }; A0D7E5BD0298A97DCBDCE40B /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */; }; @@ -765,6 +768,7 @@ B1387648C6F71F1B98244803 /* SecureBackupRecoveryKeyScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596AA8843AC1A234F3387767 /* SecureBackupRecoveryKeyScreenCoordinator.swift */; }; B14BC354E56616B6B7D9A3D7 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */; }; B188D0907A4D38AAAF6FEFA8 /* AppLockSetupFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */; }; + B1B255CE0E4306DD6E09D936 /* EncryptionResetPasswordScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A5E6F398C269AD52C9AE21 /* EncryptionResetPasswordScreenModels.swift */; }; B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */; }; B245583C63F8F90357B87FAE /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = A2AE110B053B55E38F8D10C7 /* KZFileWatchers */; }; B272E5D1DE8BDA87A6B7A696 /* RoomTimelineProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F74532E01B317C56C1BE8FA8 /* RoomTimelineProviderMock.swift */; }; @@ -926,6 +930,7 @@ D5E771132BB36240DE38102F /* RoomMessageEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */; }; D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; }; D5FE90A6AF5FD5AE91BD37C7 /* NotificationSettingsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780258F1B9D15E30549FF4BE /* NotificationSettingsEditScreenViewModel.swift */; }; + D6152E21036B88C44ECB22E7 /* EncryptionResetPasswordScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303D9438EFB481F57A366E82 /* EncryptionResetPasswordScreenViewModel.swift */; }; D63974A88CF2BC721F109C77 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = DCA3C4A997AD28E6918D4CE5 /* Compound */; }; D6661A94DBD97658B2ADBD6A /* MapTilerStaticMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */; }; D6DE764B17FB4A9A12C33BF4 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1DF3FFFE5ED2B8133F43A7 /* MessageComposer.swift */; }; @@ -954,7 +959,6 @@ DFCA89C4EC2A5332ED6B441F /* DataProtectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4959CECEC984B3995616F427 /* DataProtectionManager.swift */; }; DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */; }; DFF7D6A6C26DDD40D00AE579 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = F012CB5EE3F2B67359F6CC52 /* target.yml */; }; - E07ABB9FD1C87EBBDDE81DC5 /* ResetRecoveryKeyScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C63616A8920EC6948B31EA1B /* ResetRecoveryKeyScreenViewModel.swift */; }; E0B6A569AC3E81D233B43D60 /* SettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E625B0EB2F86B37C14EF7E6 /* SettingsScreenViewModel.swift */; }; E0C167D41A48EDB30B447DE3 /* VoiceMessageRecordingComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A5C3F7C9C1DA10CAEC6A98 /* VoiceMessageRecordingComposer.swift */; }; E0FB26262689F04D66A949D7 /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E227F34BE43B08E098796E /* TestablePreview.swift */; }; @@ -1011,7 +1015,7 @@ EB9F4688006B52E69DF5358F /* BlankFormCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7F63EB1525E697CAEB002B /* BlankFormCoordinator.swift */; }; EBE13FAB4E29738AC41BD3E5 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; EC280623A42904341363EAAF /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = A20EA00CCB9DBE0FFB17DD09 /* Collections */; }; - EC65AF0D9240A248DC9917BB /* ResetRecoveryKeyScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76911C322BC4CD117A8A0AF1 /* ResetRecoveryKeyScreenModels.swift */; }; + EC3320639828BED8B3E5F2C6 /* EncryptionResetScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5875F7C0A2398E9F134B1284 /* EncryptionResetScreenViewModel.swift */; }; ECA636DAF071C611FDC2BB57 /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; ED564C8C7C43CF5F67000368 /* PlatformViewVersionPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26813CCE39221FE30BF22CD /* PlatformViewVersionPredicate.swift */; }; ED90A59F068FD0CA27E602ED /* UserProfileListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */; }; @@ -1043,6 +1047,7 @@ F37629BAA5E8F50AAF2A131D /* SoftLogoutScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */; }; F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */; }; F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */; }; + F3F9D61C53C348043D3D6F51 /* EncryptionResetScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 811E8BF34E931D51552C9C13 /* EncryptionResetScreen.swift */; }; F40B097470D3110DFDB1FAAA /* LegalInformationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */; }; F4971845B5C4F270F6BC5745 /* ScaledFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D82F234B3576BD6268C7950 /* ScaledFrameModifier.swift */; }; F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */; }; @@ -1360,7 +1365,9 @@ 2E88534A39781D76487D59DF /* SecureBackupKeyBackupScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenViewModelTests.swift; sourceTree = ""; }; 2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsViewModelTests.swift; sourceTree = ""; }; 2F36C5D9B37E50915ECBD3EE /* RoomMemberProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxy.swift; sourceTree = ""; }; + 303D9438EFB481F57A366E82 /* EncryptionResetPasswordScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModel.swift; sourceTree = ""; }; 303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModel.swift; sourceTree = ""; }; + 306AB507E1027D6C5C147EB6 /* EncryptionResetScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenModels.swift; sourceTree = ""; }; 307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreen.swift; sourceTree = ""; }; 309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriver.swift; sourceTree = ""; }; 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerTests.swift; sourceTree = ""; }; @@ -1477,6 +1484,7 @@ 49E45C3DC740D3AB9A47FD32 /* SwipeToReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToReplyView.swift; sourceTree = ""; }; 49E6066092ED45E36BB306F7 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = ""; }; 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = ""; }; + 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = ""; }; 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = ""; }; 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = ""; }; 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = ""; }; @@ -1518,6 +1526,7 @@ 542D4F49FABA056DEEEB3400 /* RustTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustTracing.swift; sourceTree = ""; }; 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = ""; }; 5484457C81B325660901B161 /* AppLockSetupSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreen.swift; sourceTree = ""; }; + 54A5E6F398C269AD52C9AE21 /* EncryptionResetPasswordScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenModels.swift; sourceTree = ""; }; 54C4E7B46099462F12000C91 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = ""; }; 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSender.swift; sourceTree = ""; }; 5644919DB2022397D9D5825A /* MockSoftLogoutScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSoftLogoutScreenState.swift; sourceTree = ""; }; @@ -1530,6 +1539,7 @@ 57F95CADD0A5DBD76B990FCB /* ServiceLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLocator.swift; sourceTree = ""; }; 584509A363ADF6244DFDB96A /* CallNotificationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineView.swift; sourceTree = ""; }; 584A61D9C459FAFEF038A7C0 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; + 5875F7C0A2398E9F134B1284 /* EncryptionResetScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenViewModel.swift; sourceTree = ""; }; 58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelTests.swift; sourceTree = ""; }; 58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelTests.swift; sourceTree = ""; }; 592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreStaticMapView.swift; sourceTree = ""; }; @@ -1599,7 +1609,6 @@ 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsScreenIdentifier.swift; sourceTree = ""; }; 6D0A27607AB09784C8501B5C /* DeveloperOptionsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelTests.swift; sourceTree = ""; }; 6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationUITests.swift; sourceTree = ""; }; - 6DC30DEC0097B9D217493007 /* ResetRecoveryKeyScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetRecoveryKeyScreen.swift; sourceTree = ""; }; 6DF438EAFC732D2D95D34BF6 /* StartChatViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatViewModelTests.swift; sourceTree = ""; }; 6E2656184491C505700D2405 /* CollapsibleRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleRoomTimelineView.swift; sourceTree = ""; }; 6E5725BC6C63604CB769145B /* LegalInformationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModelTests.swift; sourceTree = ""; }; @@ -1636,7 +1645,6 @@ 7509AB72755DCC4B4E721B36 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/SAS.strings; sourceTree = ""; }; 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signposter.swift; sourceTree = ""; }; 76310030C831D4610A705603 /* URLComponentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponentsTests.swift; sourceTree = ""; }; - 76911C322BC4CD117A8A0AF1 /* ResetRecoveryKeyScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetRecoveryKeyScreenModels.swift; sourceTree = ""; }; 772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = ""; }; 7773CBFDBD458E0B7E270507 /* PillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillView.swift; sourceTree = ""; }; 780258F1B9D15E30549FF4BE /* NotificationSettingsEditScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModel.swift; sourceTree = ""; }; @@ -1669,6 +1677,7 @@ 7DDBF99755A9008CF8C8499E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 7DDF49CEBC0DFC59C308335F /* RoomMemberDetailsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModelProtocol.swift; sourceTree = ""; }; 7E492690C8B27A892C194CC4 /* AdvancedSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenCoordinator.swift; sourceTree = ""; }; + 7E8562F4D7DE073BC32902AB /* EncryptionResetScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenViewModelProtocol.swift; sourceTree = ""; }; 7EB58E4E8D6D634C246AD5C2 /* RoomInviterLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInviterLabel.swift; sourceTree = ""; }; 7EC2F1622C5BBABED6012E12 /* HeroImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeroImage.swift; sourceTree = ""; }; 7EECE8B331CD169790EF284F /* BugReportScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModelTests.swift; sourceTree = ""; }; @@ -1677,6 +1686,7 @@ 7FDF541AE914059942B575B4 /* IdentityConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenModels.swift; sourceTree = ""; }; 80C4927D09099497233E9980 /* WaitlistScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreen.swift; sourceTree = ""; }; 80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageEventStringBuilder.swift; sourceTree = ""; }; + 811E8BF34E931D51552C9C13 /* EncryptionResetScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreen.swift; sourceTree = ""; }; 8140010A796DB2C7977B6643 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 8166F121C79C7B62BF01D508 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pt; path = pt.lproj/Localizable.stringsdict; sourceTree = ""; }; 818695BED971753243FEF897 /* StickerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRoomTimelineItem.swift; sourceTree = ""; }; @@ -1734,6 +1744,7 @@ 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E1584F8BCF407BB94F48F04 /* EncryptionResetPasswordScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreen.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; 8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectoryCell.swift; sourceTree = ""; }; 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelTests.swift; sourceTree = ""; }; @@ -1772,7 +1783,6 @@ 989D7380D9C86B3A10D30B13 /* AppLockSetupPINScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreenViewModelTests.swift; sourceTree = ""; }; 989FC684408B31A677F5538B /* CompletionSuggestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionView.swift; sourceTree = ""; }; 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemBubbledStylerView.swift; sourceTree = ""; }; - 99637028A8BD2843A35A92D4 /* ResetRecoveryKeyScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetRecoveryKeyScreenViewModelProtocol.swift; sourceTree = ""; }; 997BF045585AF6DB2EBC5755 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFlowCoordinator.swift; sourceTree = ""; }; 9A028783CFFF861C5E44FFB1 /* BadgeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeLabel.swift; sourceTree = ""; }; @@ -1967,7 +1977,6 @@ C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreen.swift; sourceTree = ""; }; C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelProtocol.swift; sourceTree = ""; }; C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomCoordinator.swift; sourceTree = ""; }; - C63616A8920EC6948B31EA1B /* ResetRecoveryKeyScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetRecoveryKeyScreenViewModel.swift; sourceTree = ""; }; C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = ""; }; C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportUITests.swift; sourceTree = ""; }; @@ -2075,6 +2084,7 @@ E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = ""; }; E0F7CCC4A9D1927223F559D5 /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = ""; }; E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = ""; }; + E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = ""; }; E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = ""; }; E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenCoordinator.swift; sourceTree = ""; }; E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = ""; }; @@ -2103,6 +2113,7 @@ E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersView.swift; sourceTree = ""; }; E65DA46BD5CA83747AE144F3 /* secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = secrets.xcconfig; sourceTree = ""; }; E66763BD54A3A1D9C6E6F2F1 /* PinnedItemsIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedItemsIndicatorView.swift; sourceTree = ""; }; + E6935A55AB3B0C94BC566DD6 /* EncryptionResetPasswordScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenCoordinator.swift; sourceTree = ""; }; E6E6BDF9D26DB05C88901416 /* RedactedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineItem.swift; sourceTree = ""; }; E6F5D66F158A6662F953733E /* NotificationSettingsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxy.swift; sourceTree = ""; }; E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactory.swift; sourceTree = ""; }; @@ -2116,7 +2127,6 @@ E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+AttributedStringBuilder.m"; sourceTree = ""; }; E96ED747FF90332EA1333C22 /* RoomTimelineItemFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFixtures.swift; sourceTree = ""; }; E992D7B8BE54B2AB454613AF /* XCUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElement.swift; sourceTree = ""; }; - E995E3F726488F8938F0BCD1 /* ResetRecoveryKeyScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetRecoveryKeyScreenCoordinator.swift; sourceTree = ""; }; E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallInviteRoomTimelineItem.swift; sourceTree = ""; }; E9D059BFE329BE09B6D96A9F /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ro; path = ro.lproj/Localizable.stringsdict; sourceTree = ""; }; EA4D639E27D5882A6A71AECF /* GlobalSearchScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenViewModelTests.swift; sourceTree = ""; }; @@ -2322,14 +2332,6 @@ path = VoiceMessage; sourceTree = ""; }; - 01DF58ADE41080A419AD424A /* View */ = { - isa = PBXGroup; - children = ( - 6DC30DEC0097B9D217493007 /* ResetRecoveryKeyScreen.swift */, - ); - path = View; - sourceTree = ""; - }; 0210F4932B59277E2EEEF7BC /* RoomNotificationSettingsScreen */ = { isa = PBXGroup; children = ( @@ -2601,7 +2603,6 @@ 2565414373E6F68005966B8E /* SecureBackup */ = { isa = PBXGroup; children = ( - EE703B39AC7A2862CFDCA12C /* ResetKeyScreen */, B1FD4FD6CEB987AE274AEEE5 /* SecureBackupKeyBackupScreen */, 63E514D74481A3D9556DFFC3 /* SecureBackupLogoutConfirmationScreen */, 6E8F16377AD462BBD4951271 /* SecureBackupRecoveryKeyScreen */, @@ -3143,6 +3144,14 @@ path = Polls; sourceTree = ""; }; + 459B661EA3598F9E709E81A7 /* View */ = { + isa = PBXGroup; + children = ( + 8E1584F8BCF407BB94F48F04 /* EncryptionResetPasswordScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 464C6BFAA853DC755B9C1F60 /* PinnedItemsBanner */ = { isa = PBXGroup; children = ( @@ -4030,6 +4039,15 @@ path = Sources; sourceTree = ""; }; + 8656AFF06650360A5D0695FF /* EncryptionReset */ = { + isa = PBXGroup; + children = ( + 934A2E4A36A7FB6E301758EB /* EncryptionResetPasswordScreen */, + A3CF1DEFB89992E0B9187004 /* EncryptionResetScreen */, + ); + path = EncryptionReset; + sourceTree = ""; + }; 87E2774157D9C4894BCFF3F8 /* MediaPickerScreen */ = { isa = PBXGroup; children = ( @@ -4168,6 +4186,18 @@ path = ElementCall; sourceTree = ""; }; + 934A2E4A36A7FB6E301758EB /* EncryptionResetPasswordScreen */ = { + isa = PBXGroup; + children = ( + E6935A55AB3B0C94BC566DD6 /* EncryptionResetPasswordScreenCoordinator.swift */, + 54A5E6F398C269AD52C9AE21 /* EncryptionResetPasswordScreenModels.swift */, + 303D9438EFB481F57A366E82 /* EncryptionResetPasswordScreenViewModel.swift */, + 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */, + 459B661EA3598F9E709E81A7 /* View */, + ); + path = EncryptionResetPasswordScreen; + sourceTree = ""; + }; 93C7520ED23C9598BB144DBB /* UserProfileScreen */ = { isa = PBXGroup; children = ( @@ -4389,6 +4419,18 @@ path = VoiceMessage; sourceTree = ""; }; + A3CF1DEFB89992E0B9187004 /* EncryptionResetScreen */ = { + isa = PBXGroup; + children = ( + E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */, + 306AB507E1027D6C5C147EB6 /* EncryptionResetScreenModels.swift */, + 5875F7C0A2398E9F134B1284 /* EncryptionResetScreenViewModel.swift */, + 7E8562F4D7DE073BC32902AB /* EncryptionResetScreenViewModelProtocol.swift */, + D382E465AF067C1BF888BF8E /* View */, + ); + path = EncryptionResetScreen; + sourceTree = ""; + }; A448A3A8F764174C60CD0CA1 /* Other */ = { isa = PBXGroup; children = ( @@ -4861,6 +4903,14 @@ path = Layout; sourceTree = ""; }; + D382E465AF067C1BF888BF8E /* View */ = { + isa = PBXGroup; + children = ( + 811E8BF34E931D51552C9C13 /* EncryptionResetScreen.swift */, + ); + path = View; + sourceTree = ""; + }; D4B487C81A239A9C71807601 /* View */ = { isa = PBXGroup; children = ( @@ -5002,6 +5052,7 @@ 90DC2E28718955ED87AD1456 /* CreatePollScreen */, C18958141C8ED6D778F779A4 /* CreateRoom */, F5A65D1D3B83593598DC278D /* EmojiPickerScreen */, + 8656AFF06650360A5D0695FF /* EncryptionReset */, 448435400B561C40E514BE1C /* FilePreviewScreen */, 8A4738BBA7C7A299BAD70372 /* GlobalSearchScreen */, B53CA9BECD3F97805E1432D0 /* HomeScreen */, @@ -5135,18 +5186,6 @@ path = StartChatScreen; sourceTree = ""; }; - EE703B39AC7A2862CFDCA12C /* ResetKeyScreen */ = { - isa = PBXGroup; - children = ( - E995E3F726488F8938F0BCD1 /* ResetRecoveryKeyScreenCoordinator.swift */, - 76911C322BC4CD117A8A0AF1 /* ResetRecoveryKeyScreenModels.swift */, - C63616A8920EC6948B31EA1B /* ResetRecoveryKeyScreenViewModel.swift */, - 99637028A8BD2843A35A92D4 /* ResetRecoveryKeyScreenViewModelProtocol.swift */, - 01DF58ADE41080A419AD424A /* View */, - ); - path = ResetKeyScreen; - sourceTree = ""; - }; EFD4F7FCAAAB3EF45EE7A067 /* BlockedUsersScreen */ = { isa = PBXGroup; children = ( @@ -6161,6 +6200,16 @@ B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */, FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */, 46A6DB0F78FB399BD59E2D41 /* EncryptionKeyProviderProtocol.swift in Sources */, + 0C6DF318E9C8F6461E6ABDE7 /* EncryptionResetPasswordScreen.swift in Sources */, + 36926D795D6D19177C7812F8 /* EncryptionResetPasswordScreenCoordinator.swift in Sources */, + B1B255CE0E4306DD6E09D936 /* EncryptionResetPasswordScreenModels.swift in Sources */, + D6152E21036B88C44ECB22E7 /* EncryptionResetPasswordScreenViewModel.swift in Sources */, + A0601810597769B81C2358AF /* EncryptionResetPasswordScreenViewModelProtocol.swift in Sources */, + F3F9D61C53C348043D3D6F51 /* EncryptionResetScreen.swift in Sources */, + 3041EBA2660F28FFB7BDA339 /* EncryptionResetScreenCoordinator.swift in Sources */, + 97BAEDD9054FB5F233EE928B /* EncryptionResetScreenModels.swift in Sources */, + EC3320639828BED8B3E5F2C6 /* EncryptionResetScreenViewModel.swift in Sources */, + A0868BDE84D2140A885BE3C9 /* EncryptionResetScreenViewModelProtocol.swift in Sources */, 50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */, F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */, 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */, @@ -6406,11 +6455,6 @@ 46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */, 42A5A42ACF063EEE6B1980D2 /* ReportContentScreenViewModel.swift in Sources */, 8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */, - 9C9838B68C00C980A498050C /* ResetRecoveryKeyScreen.swift in Sources */, - 3D2E3E1B01D4389DCBE8F0E8 /* ResetRecoveryKeyScreenCoordinator.swift in Sources */, - EC65AF0D9240A248DC9917BB /* ResetRecoveryKeyScreenModels.swift in Sources */, - E07ABB9FD1C87EBBDDE81DC5 /* ResetRecoveryKeyScreenViewModel.swift in Sources */, - 680062C402ECB8FCAAE85A5C /* ResetRecoveryKeyScreenViewModelProtocol.swift in Sources */, A494741843F087881299ACF0 /* RestorationToken.swift in Sources */, 6E391F7F628D984AF44385D9 /* RoomAttachmentPicker.swift in Sources */, 8587A53DE8EF94FD796DC375 /* RoomAvatarImage.swift in Sources */, @@ -7509,7 +7553,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.0.29; + version = 1.0.30; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b452f42d2..19114cbe0 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -149,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "d0226f669526e908d45bf9b5682f372d84cf9ffe", - "version" : "1.0.29" + "revision" : "bc534e15fa0749d668b201b923ee57204afb868a", + "version" : "1.0.30" } }, { diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 1cdb9970d..53e7c97da 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -81,6 +81,7 @@ "action_report_bug" = "Report bug"; "action_report_content" = "Report content"; "action_reset" = "Reset"; +"action_reset_identity" = "Reset identity"; "action_retry" = "Retry"; "action_retry_decryption" = "Retry decryption"; "action_save" = "Save"; @@ -429,12 +430,20 @@ "screen_edit_profile_error_title" = "Unable to update profile"; "screen_edit_profile_title" = "Edit profile"; "screen_edit_profile_updating_details" = "Updating profile…"; +"screen_encryption_reset_bullet_1" = "Your account details, contacts, preferences, and chat list will be kept"; +"screen_encryption_reset_bullet_2" = "You will lose your existing message history"; +"screen_encryption_reset_bullet_3" = "You will need to verify all your existing devices and contacts again"; +"screen_encryption_reset_footer" = "Only reset your identity if you don’t have access to another signed-in device and you’ve lost your recovery key."; +"screen_encryption_reset_subtitle" = "If you’re not signed in to any other devices and you’ve lost your recovery key, then you’ll need to reset your identity to continue using the app. "; +"screen_encryption_reset_title" = "Reset your identity in case you can’t confirm another way"; +"screen_identity_confirmation_cannot_confirm" = "Can't confirm?"; "screen_identity_confirmation_create_new_recovery_key" = "Create a new recovery key"; "screen_identity_confirmation_subtitle" = "Verify this device to set up secure messaging."; "screen_identity_confirmation_title" = "Confirm that it's you"; +"screen_identity_confirmation_use_another_device" = "Use another device"; +"screen_identity_confirmation_use_recovery_key" = "Use recovery key"; "screen_identity_confirmed_subtitle" = "Now you can read or send messages securely, and anyone you chat with can also trust this device."; "screen_identity_confirmed_title" = "Device verified"; -"screen_identity_use_another_device" = "Use another device"; "screen_identity_waiting_on_other_device" = "Waiting on other device…"; "screen_invites_decline_chat_message" = "Are you sure you want to decline the invitation to join %1$@?"; "screen_invites_decline_chat_title" = "Decline invite"; @@ -578,6 +587,12 @@ "screen_report_content_block_user_hint" = "Check if you want to hide all current and future messages from this user"; "screen_report_content_explanation" = "This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages."; "screen_report_content_hint" = "Reason for reporting this content"; +"screen_reset_encryption_confirmation_alert_action" = "Yes, reset now"; +"screen_reset_encryption_confirmation_alert_subtitle" = "This process is irreversible."; +"screen_reset_encryption_confirmation_alert_title" = "Are you sure you want to reset your encryption?"; +"screen_reset_encryption_password_placeholder" = "Enter…"; +"screen_reset_encryption_password_subtitle" = "Confirm that you want to reset your encryption."; +"screen_reset_encryption_password_title" = "Enter your account password to continue"; "screen_room_alias_resolver_resolve_alias_failure" = "Failed to resolve room alias."; "screen_room_attachment_source_camera" = "Camera"; "screen_room_attachment_source_camera_video" = "Record video"; @@ -922,6 +937,7 @@ "screen_dm_details_unblock_user" = "Unblock user"; "screen_edit_poll_delete_confirmation_title" = "Delete Poll"; "screen_edit_poll_title" = "Edit poll"; +"screen_identity_use_another_device" = "Use another device"; "screen_login_subtitle" = "Matrix is an open network for secure, decentralised communication."; "screen_qr_code_login_invalid_scan_state_retry_button" = "Try again"; "screen_report_content_block_user" = "Block user"; diff --git a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift index 258bd8fc4..053e20e9d 100644 --- a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift @@ -26,6 +26,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { private let notificationManager: NotificationManagerProtocol private let rootNavigationStackCoordinator: NavigationStackCoordinator private let userIndicatorController: UserIndicatorControllerProtocol + private let windowManager: WindowManagerProtocol private let isNewLogin: Bool private var navigationStackCoordinator: NavigationStackCoordinator! @@ -57,6 +58,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { notificationManager: NotificationManagerProtocol, navigationStackCoordinator: NavigationStackCoordinator, userIndicatorController: UserIndicatorControllerProtocol, + windowManager: WindowManagerProtocol, isNewLogin: Bool) { self.userSession = userSession self.appLockService = appLockService @@ -64,6 +66,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { self.appSettings = appSettings self.notificationManager = notificationManager self.userIndicatorController = userIndicatorController + self.windowManager = windowManager self.isNewLogin = isNewLogin rootNavigationStackCoordinator = navigationStackCoordinator @@ -215,7 +218,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { appSettings.hasRunIdentityConfirmationOnboarding = true stateMachine.tryEvent(.next) case .reset: - presentResetRecoveryKeyScreen() + presentEncryptionResetScreen() } } .store(in: &cancellables) @@ -262,8 +265,8 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { case .recoveryFixed: appSettings.hasRunIdentityConfirmationOnboarding = true stateMachine.tryEvent(.next) - case .showResetKeyInfo: - presentResetRecoveryKeyScreen() + case .resetEncryption: + presentEncryptionResetScreen() default: MXLog.error("Unexpected recovery action: \(action)") } @@ -273,16 +276,32 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { presentCoordinator(coordinator) } - private func presentResetRecoveryKeyScreen() { - let coordinator = ResetRecoveryKeyScreenCoordinator() + private func presentEncryptionResetScreen() { + let resetNavigationStackCoordinator = NavigationStackCoordinator() + + let coordinator = EncryptionResetScreenCoordinator(parameters: .init(clientProxy: userSession.clientProxy, + navigationStackCoordinator: resetNavigationStackCoordinator, + userIndicatorController: userIndicatorController)) + coordinator.actionsPublisher.sink { [weak self] action in + guard let self else { return } + switch action { case .cancel: - self?.navigationStackCoordinator.setSheetCoordinator(nil) + navigationStackCoordinator.setSheetCoordinator(nil) + case .requestOIDCAuthorisation(let url): + presentOIDCAuthorisationScreen(url: url) + case .resetFinished: + appSettings.hasRunIdentityConfirmationOnboarding = true + stateMachine.tryEvent(.next) + navigationStackCoordinator.setSheetCoordinator(nil) } } .store(in: &cancellables) - navigationStackCoordinator.setSheetCoordinator(coordinator) + + resetNavigationStackCoordinator.setRootCoordinator(coordinator) + + navigationStackCoordinator.setSheetCoordinator(resetNavigationStackCoordinator) } private func presentIdentityConfirmedScreen() { @@ -361,4 +380,12 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { navigationStackCoordinator.push(coordinator) } } + + 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() + } } diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 1d8c0a295..07489230e 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -153,10 +153,18 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { private func presentSecureBackupScreen(animated: Bool) { let coordinator = SecureBackupScreenCoordinator(parameters: .init(appSettings: parameters.appSettings, - secureBackupController: parameters.userSession.clientProxy.secureBackupController, + clientProxy: parameters.userSession.clientProxy, navigationStackCoordinator: navigationStackCoordinator, userIndicatorController: parameters.userIndicatorController)) + coordinator.actions.sink { [weak self] action in + switch action { + case .requestOIDCAuthorisation(let url): + self?.presentAccountManagementURL(url) + } + } + .store(in: &cancellables) + navigationStackCoordinator.push(coordinator, animated: animated) } diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 44a9ae871..e1afba8bc 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -115,6 +115,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { notificationManager: notificationManager, navigationStackCoordinator: detailNavigationStackCoordinator, userIndicatorController: ServiceLocator.shared.userIndicatorController, + windowManager: appMediator.windowManager, isNewLogin: isNewLogin) setupStateMachine() diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 708ea61ac..cfee57a6c 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -196,6 +196,8 @@ internal enum L10n { internal static var actionReportContent: String { return L10n.tr("Localizable", "action_report_content") } /// Reset internal static var actionReset: String { return L10n.tr("Localizable", "action_reset") } + /// Reset identity + internal static var actionResetIdentity: String { return L10n.tr("Localizable", "action_reset_identity") } /// Retry internal static var actionRetry: String { return L10n.tr("Localizable", "action_retry") } /// Retry decryption @@ -1045,12 +1047,30 @@ internal enum L10n { internal static var screenEditProfileTitle: String { return L10n.tr("Localizable", "screen_edit_profile_title") } /// Updating profile… internal static var screenEditProfileUpdatingDetails: String { return L10n.tr("Localizable", "screen_edit_profile_updating_details") } + /// Your account details, contacts, preferences, and chat list will be kept + internal static var screenEncryptionResetBullet1: String { return L10n.tr("Localizable", "screen_encryption_reset_bullet_1") } + /// You will lose your existing message history + internal static var screenEncryptionResetBullet2: String { return L10n.tr("Localizable", "screen_encryption_reset_bullet_2") } + /// You will need to verify all your existing devices and contacts again + internal static var screenEncryptionResetBullet3: String { return L10n.tr("Localizable", "screen_encryption_reset_bullet_3") } + /// Only reset your identity if you don’t have access to another signed-in device and you’ve lost your recovery key. + internal static var screenEncryptionResetFooter: String { return L10n.tr("Localizable", "screen_encryption_reset_footer") } + /// If you’re not signed in to any other devices and you’ve lost your recovery key, then you’ll need to reset your identity to continue using the app. + internal static var screenEncryptionResetSubtitle: String { return L10n.tr("Localizable", "screen_encryption_reset_subtitle") } + /// Reset your identity in case you can’t confirm another way + internal static var screenEncryptionResetTitle: String { return L10n.tr("Localizable", "screen_encryption_reset_title") } + /// Can't confirm? + internal static var screenIdentityConfirmationCannotConfirm: String { return L10n.tr("Localizable", "screen_identity_confirmation_cannot_confirm") } /// Create a new recovery key internal static var screenIdentityConfirmationCreateNewRecoveryKey: String { return L10n.tr("Localizable", "screen_identity_confirmation_create_new_recovery_key") } /// Verify this device to set up secure messaging. internal static var screenIdentityConfirmationSubtitle: String { return L10n.tr("Localizable", "screen_identity_confirmation_subtitle") } /// Confirm that it's you internal static var screenIdentityConfirmationTitle: String { return L10n.tr("Localizable", "screen_identity_confirmation_title") } + /// Use another device + internal static var screenIdentityConfirmationUseAnotherDevice: String { return L10n.tr("Localizable", "screen_identity_confirmation_use_another_device") } + /// Use recovery key + internal static var screenIdentityConfirmationUseRecoveryKey: String { return L10n.tr("Localizable", "screen_identity_confirmation_use_recovery_key") } /// Now you can read or send messages securely, and anyone you chat with can also trust this device. internal static var screenIdentityConfirmedSubtitle: String { return L10n.tr("Localizable", "screen_identity_confirmed_subtitle") } /// Device verified @@ -1385,6 +1405,18 @@ internal enum L10n { internal static var screenReportContentExplanation: String { return L10n.tr("Localizable", "screen_report_content_explanation") } /// Reason for reporting this content internal static var screenReportContentHint: String { return L10n.tr("Localizable", "screen_report_content_hint") } + /// Yes, reset now + internal static var screenResetEncryptionConfirmationAlertAction: String { return L10n.tr("Localizable", "screen_reset_encryption_confirmation_alert_action") } + /// This process is irreversible. + internal static var screenResetEncryptionConfirmationAlertSubtitle: String { return L10n.tr("Localizable", "screen_reset_encryption_confirmation_alert_subtitle") } + /// Are you sure you want to reset your encryption? + internal static var screenResetEncryptionConfirmationAlertTitle: String { return L10n.tr("Localizable", "screen_reset_encryption_confirmation_alert_title") } + /// Enter… + internal static var screenResetEncryptionPasswordPlaceholder: String { return L10n.tr("Localizable", "screen_reset_encryption_password_placeholder") } + /// Confirm that you want to reset your encryption. + internal static var screenResetEncryptionPasswordSubtitle: String { return L10n.tr("Localizable", "screen_reset_encryption_password_subtitle") } + /// Enter your account password to continue + internal static var screenResetEncryptionPasswordTitle: String { return L10n.tr("Localizable", "screen_reset_encryption_password_title") } /// Failed to resolve room alias. internal static var screenRoomAliasResolverResolveAliasFailure: String { return L10n.tr("Localizable", "screen_room_alias_resolver_resolve_alias_failure") } /// Camera diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index f8b228ec2..8dad86fca 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -4095,6 +4095,70 @@ class ClientProxyMock: ClientProxyProtocol { return curve25519Base64ReturnValue } } + //MARK: - resetIdentity + + var resetIdentityUnderlyingCallsCount = 0 + var resetIdentityCallsCount: Int { + get { + if Thread.isMainThread { + return resetIdentityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = resetIdentityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + resetIdentityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + resetIdentityUnderlyingCallsCount = newValue + } + } + } + } + var resetIdentityCalled: Bool { + return resetIdentityCallsCount > 0 + } + + var resetIdentityUnderlyingReturnValue: Result! + var resetIdentityReturnValue: Result! { + get { + if Thread.isMainThread { + return resetIdentityUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = resetIdentityUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + resetIdentityUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + resetIdentityUnderlyingReturnValue = newValue + } + } + } + } + var resetIdentityClosure: (() async -> Result)? + + func resetIdentity() async -> Result { + resetIdentityCallsCount += 1 + if let resetIdentityClosure = resetIdentityClosure { + return await resetIdentityClosure() + } else { + return resetIdentityReturnValue + } + } //MARK: - loadMediaContentForSource var loadMediaContentForSourceThrowableError: Error? diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index a37d07a20..5142c895a 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -5915,6 +5915,75 @@ open class EncryptionSDKMock: MatrixRustSDK.Encryption { } } + //MARK: - resetIdentity + + open var resetIdentityThrowableError: Error? + var resetIdentityUnderlyingCallsCount = 0 + open var resetIdentityCallsCount: Int { + get { + if Thread.isMainThread { + return resetIdentityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = resetIdentityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + resetIdentityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + resetIdentityUnderlyingCallsCount = newValue + } + } + } + } + open var resetIdentityCalled: Bool { + return resetIdentityCallsCount > 0 + } + + var resetIdentityUnderlyingReturnValue: IdentityResetHandle? + open var resetIdentityReturnValue: IdentityResetHandle? { + get { + if Thread.isMainThread { + return resetIdentityUnderlyingReturnValue + } else { + var returnValue: IdentityResetHandle?? = nil + DispatchQueue.main.sync { + returnValue = resetIdentityUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + resetIdentityUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + resetIdentityUnderlyingReturnValue = newValue + } + } + } + } + open var resetIdentityClosure: (() async throws -> IdentityResetHandle?)? + + open override func resetIdentity() async throws -> IdentityResetHandle? { + if let error = resetIdentityThrowableError { + throw error + } + resetIdentityCallsCount += 1 + if let resetIdentityClosure = resetIdentityClosure { + return try await resetIdentityClosure() + } else { + return resetIdentityReturnValue + } + } + //MARK: - resetRecoveryKey open var resetRecoveryKeyThrowableError: Error? @@ -7595,6 +7664,128 @@ open class HomeserverLoginDetailsSDKMock: MatrixRustSDK.HomeserverLoginDetails { } } } +open class IdentityResetHandleSDKMock: MatrixRustSDK.IdentityResetHandle { + init() { + super.init(noPointer: .init()) + } + + public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + fatalError("init(unsafeFromRawPointer:) has not been implemented") + } + + fileprivate var pointer: UnsafeMutableRawPointer! + + //MARK: - authType + + var authTypeUnderlyingCallsCount = 0 + open var authTypeCallsCount: Int { + get { + if Thread.isMainThread { + return authTypeUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = authTypeUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + authTypeUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + authTypeUnderlyingCallsCount = newValue + } + } + } + } + open var authTypeCalled: Bool { + return authTypeCallsCount > 0 + } + + var authTypeUnderlyingReturnValue: CrossSigningResetAuthType! + open var authTypeReturnValue: CrossSigningResetAuthType! { + get { + if Thread.isMainThread { + return authTypeUnderlyingReturnValue + } else { + var returnValue: CrossSigningResetAuthType? = nil + DispatchQueue.main.sync { + returnValue = authTypeUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + authTypeUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + authTypeUnderlyingReturnValue = newValue + } + } + } + } + open var authTypeClosure: (() -> CrossSigningResetAuthType)? + + open override func authType() -> CrossSigningResetAuthType { + authTypeCallsCount += 1 + if let authTypeClosure = authTypeClosure { + return authTypeClosure() + } else { + return authTypeReturnValue + } + } + + //MARK: - reset + + open var resetAuthThrowableError: Error? + var resetAuthUnderlyingCallsCount = 0 + open var resetAuthCallsCount: Int { + get { + if Thread.isMainThread { + return resetAuthUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = resetAuthUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + resetAuthUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + resetAuthUnderlyingCallsCount = newValue + } + } + } + } + open var resetAuthCalled: Bool { + return resetAuthCallsCount > 0 + } + open var resetAuthReceivedAuth: AuthData? + open var resetAuthReceivedInvocations: [AuthData?] = [] + open var resetAuthClosure: ((AuthData?) async throws -> Void)? + + open override func reset(auth: AuthData?) async throws { + if let error = resetAuthThrowableError { + throw error + } + resetAuthCallsCount += 1 + resetAuthReceivedAuth = auth + DispatchQueue.main.async { + self.resetAuthReceivedInvocations.append(auth) + } + try await resetAuthClosure?(auth) + } +} open class MediaFileHandleSDKMock: MatrixRustSDK.MediaFileHandle { init() { super.init(noPointer: .init()) diff --git a/ElementX/Sources/Other/SwiftUI/Views/RoundedLabelItem.swift b/ElementX/Sources/Other/SwiftUI/Views/RoundedLabelItem.swift index 162bfe0eb..bd30bf088 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/RoundedLabelItem.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/RoundedLabelItem.swift @@ -58,12 +58,11 @@ struct RoundedLabelItem: View { private struct CheckmarkLabelStyle: LabelStyle { func makeBody(configuration: Configuration) -> some View { - HStack(alignment: .firstTextBaseline, spacing: 16) { + HStack(alignment: .top, spacing: 16) { configuration.icon - .font(.compound.bodyLGSemibold) configuration.title - .font(.compound.bodyMDSemibold) } + .font(.compound.bodyMD) .foregroundColor(.compound.textPrimary) } } diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenCoordinator.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenCoordinator.swift new file mode 100644 index 000000000..e87962686 --- /dev/null +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenCoordinator.swift @@ -0,0 +1,68 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// periphery:ignore:all - this is just a encryptionResetPassword remove this comment once generating the final file + +import Combine +import SwiftUI + +struct EncryptionResetPasswordScreenCoordinatorParameters { } + +enum EncryptionResetPasswordScreenCoordinatorAction: CustomStringConvertible { + case resetIdentity(String) + + var description: String { + switch self { + case .resetIdentity: + "resetIdentity" + } + } +} + +final class EncryptionResetPasswordScreenCoordinator: CoordinatorProtocol { + private let parameters: EncryptionResetPasswordScreenCoordinatorParameters + private let viewModel: EncryptionResetPasswordScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: EncryptionResetPasswordScreenCoordinatorParameters) { + self.parameters = parameters + + viewModel = EncryptionResetPasswordScreenViewModel() + } + + func start() { + viewModel.actionsPublisher.sink { [weak self] action in + MXLog.info("Coordinator: received view model action: \(action)") + + guard let self else { return } + switch action { + case .resetIdentity(let password): + self.actionsSubject.send(.resetIdentity(password)) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(EncryptionResetPasswordScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenModels.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenModels.swift new file mode 100644 index 000000000..f40ac6b30 --- /dev/null +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenModels.swift @@ -0,0 +1,41 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +enum EncryptionResetPasswordScreenViewModelAction: CustomStringConvertible { + case resetIdentity(String) + + var description: String { + switch self { + case .resetIdentity: + "resetIdentity" + } + } +} + +struct EncryptionResetPasswordScreenViewState: BindableState { + var bindings: EncryptionResetPasswordScreenViewStateBindings +} + +struct EncryptionResetPasswordScreenViewStateBindings { + var password: String + var alertInfo: AlertInfo? +} + +enum EncryptionResetPasswordScreenViewAction { + case resetIdentity +} diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenViewModel.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenViewModel.swift new file mode 100644 index 000000000..46dd30899 --- /dev/null +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenViewModel.swift @@ -0,0 +1,42 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +typealias EncryptionResetPasswordScreenViewModelType = StateStoreViewModel + +class EncryptionResetPasswordScreenViewModel: EncryptionResetPasswordScreenViewModelType, EncryptionResetPasswordScreenViewModelProtocol { + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init() { + super.init(initialViewState: .init(bindings: .init(password: ""))) + } + + // MARK: - Public + + override func process(viewAction: EncryptionResetPasswordScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + + switch viewAction { + case .resetIdentity: + actionsSubject.send(.resetIdentity(state.bindings.password)) + } + } +} diff --git a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenViewModelProtocol.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenViewModelProtocol.swift similarity index 72% rename from ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenViewModelProtocol.swift rename to ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenViewModelProtocol.swift index 842fa48b3..1db9d7c7c 100644 --- a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/EncryptionResetPasswordScreenViewModelProtocol.swift @@ -17,7 +17,7 @@ import Combine @MainActor -protocol ResetRecoveryKeyScreenViewModelProtocol { - var actionsPublisher: AnyPublisher { get } - var context: ResetRecoveryKeyScreenViewModelType.Context { get } +protocol EncryptionResetPasswordScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: EncryptionResetPasswordScreenViewModelType.Context { get } } diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/View/EncryptionResetPasswordScreen.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/View/EncryptionResetPasswordScreen.swift new file mode 100644 index 000000000..9a6216347 --- /dev/null +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetPasswordScreen/View/EncryptionResetPasswordScreen.swift @@ -0,0 +1,83 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Compound +import SwiftUI + +struct EncryptionResetPasswordScreen: View { + @ObservedObject var context: EncryptionResetPasswordScreenViewModel.Context + @FocusState private var textFieldFocus + + var body: some View { + FullscreenDialog { + VStack(spacing: 16) { + HeroImage(icon: \.lockSolid) + + Text(L10n.screenResetEncryptionPasswordTitle) + .foregroundColor(.compound.textPrimary) + .font(.compound.headingMDBold) + .multilineTextAlignment(.center) + + Text(L10n.screenResetEncryptionPasswordSubtitle) + .foregroundColor(.compound.textSecondary) + .font(.compound.bodyMD) + .multilineTextAlignment(.center) + + passwordSection + } + .padding(16) + } bottomContent: { + Button(L10n.actionResetIdentity, role: .destructive) { + context.send(viewAction: .resetIdentity) + } + .buttonStyle(.compound(.primary)) + } + .backgroundStyle(.compound.bgCanvasDefault) + .interactiveDismissDisabled() + .onAppear { textFieldFocus = true } + } + + @ViewBuilder + private var passwordSection: some View { + VStack(alignment: .leading, spacing: 8) { + Text(L10n.commonPassword) + .foregroundColor(.compound.textPrimary) + .font(.compound.bodySMSemibold) + + SecureField(L10n.screenResetEncryptionPasswordPlaceholder, text: $context.password) + .frame(maxWidth: .infinity) + .padding() + .background(Color.compound.bgSubtleSecondaryLevel0) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .focused($textFieldFocus) + .submitLabel(.done) + .onSubmit { + context.send(viewAction: .resetIdentity) + } + } + } +} + +// MARK: - Previews + +struct EncryptionResetPasswordScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = EncryptionResetPasswordScreenViewModel() + static var previews: some View { + NavigationStack { + EncryptionResetPasswordScreen(context: viewModel.context) + } + } +} diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenCoordinator.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenCoordinator.swift new file mode 100644 index 000000000..56bb0392b --- /dev/null +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenCoordinator.swift @@ -0,0 +1,90 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import SwiftUI + +enum EncryptionResetScreenCoordinatorAction { + case cancel + case requestOIDCAuthorisation(URL) + case resetFinished +} + +struct EncryptionResetScreenCoordinatorParameters { + let clientProxy: ClientProxyProtocol + let navigationStackCoordinator: NavigationStackCoordinator + let userIndicatorController: UserIndicatorControllerProtocol +} + +final class EncryptionResetScreenCoordinator: CoordinatorProtocol { + private let parameters: EncryptionResetScreenCoordinatorParameters + private let viewModel: EncryptionResetScreenViewModelProtocol + + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: EncryptionResetScreenCoordinatorParameters) { + self.parameters = parameters + viewModel = EncryptionResetScreenViewModel(clientProxy: parameters.clientProxy, + userIndicatorController: parameters.userIndicatorController) + } + + func start() { + viewModel.actionsPublisher.sink { [weak self] action in + MXLog.info("Coordinator: received view model action: \(action)") + + guard let self else { return } + switch action { + case .requestPassword: + presentPasswordScreen() + case .requestOIDCAuthorisation(let url): + self.actionsSubject.send(.requestOIDCAuthorisation(url)) + case .resetFinished: + self.actionsSubject.send(.resetFinished) + case .cancel: + self.actionsSubject.send(.cancel) + } + } + .store(in: &cancellables) + } + + 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) + } +} diff --git a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenModels.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenModels.swift similarity index 79% rename from ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenModels.swift rename to ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenModels.swift index 82f91dd81..335e7682b 100644 --- a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenModels.swift +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenModels.swift @@ -16,11 +16,14 @@ import Foundation -enum ResetRecoveryKeyScreenViewModelAction { +enum EncryptionResetScreenViewModelAction { + case requestPassword + case requestOIDCAuthorisation(url: URL) + case resetFinished case cancel } -struct ResetRecoveryKeyScreenViewState: BindableState { +struct EncryptionResetScreenViewState: BindableState { private let listItem3AttributedText = { let boldPlaceholder = "{bold}" var finalString = AttributedString(L10n.screenCreateNewRecoveryKeyListItem3(boldPlaceholder)) @@ -39,8 +42,15 @@ struct ResetRecoveryKeyScreenViewState: BindableState { AttributedString(L10n.screenCreateNewRecoveryKeyListItem5) ] } + + var bindings: EncryptionResetScreenViewStateBindings } -enum ResetRecoveryKeyScreenViewAction { +struct EncryptionResetScreenViewStateBindings { + var alertInfo: AlertInfo? +} + +enum EncryptionResetScreenViewAction { + case reset case cancel } diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenViewModel.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenViewModel.swift new file mode 100644 index 000000000..61db33d49 --- /dev/null +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenViewModel.swift @@ -0,0 +1,156 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import MatrixRustSDK +import SwiftUI + +typealias EncryptionResetScreenViewModelType = StateStoreViewModel + +class EncryptionResetScreenViewModel: EncryptionResetScreenViewModelType, EncryptionResetScreenViewModelProtocol { + private let clientProxy: ClientProxyProtocol + private let userIndicatorController: UserIndicatorControllerProtocol + + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + private var identityResetHandle: IdentityResetHandle? + + init(clientProxy: ClientProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) { + self.clientProxy = clientProxy + self.userIndicatorController = userIndicatorController + + super.init(initialViewState: EncryptionResetScreenViewState(bindings: .init())) + } + + // MARK: - Public + + override func process(viewAction: EncryptionResetScreenViewAction) { + switch viewAction { + case .reset: + state.bindings.alertInfo = .init(id: UUID(), + title: L10n.screenResetEncryptionConfirmationAlertTitle, + message: L10n.screenResetEncryptionConfirmationAlertSubtitle, + primaryButton: .init(title: L10n.screenResetEncryptionConfirmationAlertAction, role: .destructive, action: { [weak self] in + guard let self else { return } + Task { await self.startResetFlow() } + })) + case .cancel: + actionsSubject.send(.cancel) + } + } + + func continueResetFlowWith(password: String) { + Task { + await resetWith(password: password) + } + } + + // MARK: - Private + + private func startResetFlow() async { + showLoadingIndicator() + + defer { + hideLoadingIndicator() + } + + switch await clientProxy.resetIdentity() { + case .success(let handle): + // If the handle is missing then interactive authentication wasn't + // necessary and the reset proceeded as normal + guard let handle else { + actionsSubject.send(.resetFinished) + return + } + + identityResetHandle = handle + + switch handle.authType() { + case .uiaa: + actionsSubject.send(.requestPassword) + case .oidc(let oidcInfo): + guard let url = URL(string: oidcInfo.approvalUrl) else { + fatalError("Invalid URL received through identity reset handle: \(oidcInfo.approvalUrl)") + } + + hideLoadingIndicator() + + actionsSubject.send(.requestOIDCAuthorisation(url: url)) + + await resetWithOIDCAuthorisation() + } + case .failure(let error): + MXLog.error("Failed resetting encryption with error \(error)") + showErrorToast() + } + } + + func resetWith(password: String) async { + guard let identityResetHandle else { + fatalError("Requested reset flow continuation without a stored handle") + } + + showLoadingIndicator() + + defer { + hideLoadingIndicator() + } + + do { + try await identityResetHandle.reset(auth: .password(passwordDetails: .init(identifier: clientProxy.userID, password: password))) + actionsSubject.send(.resetFinished) + } catch { + MXLog.error("Failed resetting encryption with error \(error)") + showErrorToast() + } + } + + private func resetWithOIDCAuthorisation() async { + guard let identityResetHandle else { + fatalError("Requested reset flow continuation without a stored handle") + } + + do { + try await identityResetHandle.reset(auth: nil) + actionsSubject.send(.resetFinished) + } catch { + MXLog.error("Failed resetting encryption with error \(error)") + showErrorToast() + } + } + + // MARK: Toasts and loading indicators + + private static let loadingIndicatorIdentifier = "\(EncryptionResetScreenViewModel.self)-Loading" + + private func showLoadingIndicator() { + userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, + type: .modal, + title: L10n.commonLoading, + persistent: true)) + } + + private func hideLoadingIndicator() { + userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) + } + + private func showErrorToast() { + userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown)) + } +} diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenViewModelProtocol.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenViewModelProtocol.swift new file mode 100644 index 000000000..fe96ce0e8 --- /dev/null +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/EncryptionResetScreenViewModelProtocol.swift @@ -0,0 +1,25 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine + +@MainActor +protocol EncryptionResetScreenViewModelProtocol { + var actionsPublisher: AnyPublisher { get } + var context: EncryptionResetScreenViewModelType.Context { get } + + func continueResetFlowWith(password: String) +} diff --git a/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/View/EncryptionResetScreen.swift b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/View/EncryptionResetScreen.swift new file mode 100644 index 000000000..24924359d --- /dev/null +++ b/ElementX/Sources/Screens/EncryptionReset/EncryptionResetScreen/View/EncryptionResetScreen.swift @@ -0,0 +1,119 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Compound +import SwiftUI + +struct EncryptionResetScreen: View { + @ObservedObject var context: EncryptionResetScreenViewModel.Context + + var body: some View { + FullscreenDialog { + mainContent + } bottomContent: { + Button(L10n.actionContinue, role: .destructive) { + context.send(viewAction: .reset) + } + .buttonStyle(.compound(.primary)) + } + .background() + .backgroundStyle(.compound.bgSubtleSecondary) + .interactiveDismissDisabled() + .toolbar { toolbar } + .toolbar(.visible, for: .navigationBar) + .alert(item: $context.alertInfo) + } + + /// The main content of the screen that is shown inside the scroll view. + private var mainContent: some View { + VStack(spacing: 24) { + header + checkmarkList + footer + } + } + + private var header: some View { + VStack(spacing: 8) { + HeroImage(icon: \.error, style: .critical) + .padding(.bottom, 8) + + Text(L10n.screenEncryptionResetTitle) + .font(.compound.headingMDBold) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textPrimary) + + Text(L10n.screenEncryptionResetSubtitle) + .font(.compound.bodyMD) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textSecondary) + } + } + + private var footer: some View { + Text(L10n.screenEncryptionResetFooter) + .font(.compound.bodyMDSemibold) + .multilineTextAlignment(.center) + .foregroundColor(.compound.textPrimary) + } + + /// The list of re-assurances about analytics. + private var checkmarkList: some View { + VStack(alignment: .leading, spacing: 4) { + checkMarkItem(title: L10n.screenEncryptionResetBullet1, position: .top, positive: true) + checkMarkItem(title: L10n.screenEncryptionResetBullet2, position: .middle, positive: false) + checkMarkItem(title: L10n.screenEncryptionResetBullet3, position: .bottom, positive: false) + } + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity) + .environment(\.backgroundStyle, AnyShapeStyle(.compound.bgSubtleSecondary)) + } + + @ViewBuilder + private func checkMarkItem(title: String, position: ListPosition, positive: Bool) -> some View { + RoundedLabelItem(title: title, listPosition: position) { + if positive { + CompoundIcon(\.check) + .foregroundColor(.compound.iconAccentPrimary) + } else { + CompoundIcon(\.close) + .foregroundColor(.compound.iconCriticalPrimary) + } + } + .backgroundStyle(.compound.bgCanvasDefault) + } + + @ToolbarContentBuilder + private var toolbar: some ToolbarContent { + ToolbarItem(placement: .cancellationAction) { + Button(L10n.actionCancel) { + context.send(viewAction: .cancel) + } + } + } +} + +// MARK: - Previews + +struct EncryptionResetScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = EncryptionResetScreenViewModel(clientProxy: ClientProxyMock(.init()), + userIndicatorController: UserIndicatorControllerMock()) + static var previews: some View { + NavigationStack { + EncryptionResetScreen(context: viewModel.context) + } + } +} diff --git a/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift index a56242eee..71b193797 100644 --- a/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift +++ b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Compound import SwiftUI /// A prompt that asks the user whether they would like to enable Analytics or not. @@ -58,14 +59,6 @@ struct AnalyticsPromptScreen: View { .foregroundColor(.compound.textSecondary) } } - - @ViewBuilder - private var checkMark: some View { - Image(systemName: "checkmark.circle") - .symbolVariant(.fill) - .symbolRenderingMode(.palette) - .foregroundStyle(Color.compound.iconAccentTertiary, Color.compound.textOnSolidPrimary) - } /// The list of re-assurances about analytics. private var checkmarkList: some View { @@ -82,7 +75,8 @@ struct AnalyticsPromptScreen: View { @ViewBuilder private func checkMarkItem(title: String, position: ListPosition) -> some View { RoundedLabelItem(title: title, listPosition: position) { - checkMark + CompoundIcon(\.checkCircle, size: .small, relativeTo: .body) + .foregroundColor(.compound.iconAccentPrimary) } } diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift index b4e4ba045..5e99d8881 100644 --- a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift @@ -70,21 +70,21 @@ struct IdentityConfirmationScreen: View { @ViewBuilder private var actionButtons: some View { - VStack(spacing: 32) { + VStack(spacing: 16) { if context.viewState.availableActions.contains(.interactiveVerification) { - Button(L10n.actionStartVerification) { + Button(L10n.screenIdentityConfirmationUseAnotherDevice) { context.send(viewAction: .otherDevice) } .buttonStyle(.compound(.primary)) if context.viewState.availableActions.contains(.recovery) { - Button(L10n.screenSessionVerificationEnterRecoveryKey) { + Button(L10n.screenIdentityConfirmationUseRecoveryKey) { context.send(viewAction: .recoveryKey) } - .buttonStyle(.compound(.plain)) + .buttonStyle(.compound(.secondary)) } } else if context.viewState.availableActions.contains(.recovery) { - Button(L10n.screenSessionVerificationEnterRecoveryKey) { + Button(L10n.screenIdentityConfirmationUseRecoveryKey) { context.send(viewAction: .recoveryKey) } .buttonStyle(.compound(.primary)) @@ -97,10 +97,11 @@ struct IdentityConfirmationScreen: View { .buttonStyle(.compound(.plain)) } - Button(L10n.screenRecoveryKeyConfirmLostRecoveryKey, role: .destructive) { + Button(L10n.screenIdentityConfirmationCannotConfirm) { context.send(viewAction: .reset) } .buttonStyle(.compound(.plain)) + .padding(.vertical, 14) } } } @@ -112,11 +113,13 @@ struct IdentityConfirmationScreen_Previews: PreviewProvider, TestablePreview { NavigationStack { IdentityConfirmationScreen(context: viewModel.context) } + .snapshot(delay: 0.25) } private static var viewModel: IdentityConfirmationScreenViewModel { - let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@user:example.com", - roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))))))) + let clientProxy = ClientProxyMock(.init()) + let userSession = UserSessionMock(.init(clientProxy: clientProxy)) + userSession.sessionSecurityStatePublisher = CurrentValuePublisher(.init(verificationState: .unverified, recoveryState: .enabled)) return IdentityConfirmationScreenViewModel(userSession: userSession, appSettings: ServiceLocator.shared.settings, diff --git a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenCoordinator.swift b/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenCoordinator.swift deleted file mode 100644 index 986ed1495..000000000 --- a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenCoordinator.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// periphery:ignore:all - this is just a resetKey remove this comment once generating the final file - -import Combine -import SwiftUI - -enum ResetRecoveryKeyScreenCoordinatorAction { - case cancel -} - -final class ResetRecoveryKeyScreenCoordinator: CoordinatorProtocol { - private let viewModel: ResetRecoveryKeyScreenViewModelProtocol - - private var cancellables = Set() - - private let actionsSubject: PassthroughSubject = .init() - var actionsPublisher: AnyPublisher { - actionsSubject.eraseToAnyPublisher() - } - - init() { - viewModel = ResetRecoveryKeyScreenViewModel() - } - - func start() { - viewModel.actionsPublisher.sink { [weak self] action in - MXLog.info("Coordinator: received view model action: \(action)") - - guard let self else { return } - switch action { - case .cancel: - self.actionsSubject.send(.cancel) - } - } - .store(in: &cancellables) - } - - func toPresentable() -> AnyView { - AnyView(ResetRecoveryKeyScreen(context: viewModel.context)) - } -} diff --git a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenViewModel.swift b/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenViewModel.swift deleted file mode 100644 index 45763535c..000000000 --- a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/ResetRecoveryKeyScreenViewModel.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Combine -import SwiftUI - -typealias ResetRecoveryKeyScreenViewModelType = StateStoreViewModel - -class ResetRecoveryKeyScreenViewModel: ResetRecoveryKeyScreenViewModelType, ResetRecoveryKeyScreenViewModelProtocol { - private let actionsSubject: PassthroughSubject = .init() - var actionsPublisher: AnyPublisher { - actionsSubject.eraseToAnyPublisher() - } - - init() { - super.init(initialViewState: ResetRecoveryKeyScreenViewState()) - } - - // MARK: - Public - - override func process(viewAction: ResetRecoveryKeyScreenViewAction) { - switch viewAction { - case .cancel: - // We might also need to display first an alert and do a logOut API call in some cases - actionsSubject.send(.cancel) - } - } -} diff --git a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/View/ResetRecoveryKeyScreen.swift b/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/View/ResetRecoveryKeyScreen.swift deleted file mode 100644 index c1abf497c..000000000 --- a/ElementX/Sources/Screens/SecureBackup/ResetKeyScreen/View/ResetRecoveryKeyScreen.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Compound -import SwiftUI - -struct ResetRecoveryKeyScreen: View { - @ObservedObject var context: ResetRecoveryKeyScreenViewModel.Context - - var body: some View { - NavigationStack { - FullscreenDialog { - mainContent - } bottomContent: { - EmptyView() - } - .toolbar { toolbar } - .toolbar(.visible, for: .navigationBar) - .background() - .backgroundStyle(.compound.bgSubtleSecondary) - .interactiveDismissDisabled() - } - } - - private var mainContent: some View { - VStack(spacing: 40) { - header - SFNumberedListView(items: context.viewState.listItems) - } - } - - private var header: some View { - VStack(spacing: 16) { - HeroImage(icon: \.computer, style: .subtle) - - Text(L10n.screenCreateNewRecoveryKeyTitle) - .foregroundColor(.compound.textPrimary) - .font(.compound.headingMDBold) - .multilineTextAlignment(.center) - } - } - - @ToolbarContentBuilder - private var toolbar: some ToolbarContent { - ToolbarItem(placement: .cancellationAction) { - Button(L10n.actionCancel) { - context.send(viewAction: .cancel) - } - } - } -} - -// MARK: - Previews - -struct ResetRecoveryKeyScreen_Previews: PreviewProvider, TestablePreview { - static let viewModel = ResetRecoveryKeyScreenViewModel() - static var previews: some View { - NavigationStack { - ResetRecoveryKeyScreen(context: viewModel.context) - } - } -} diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenCoordinator.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenCoordinator.swift index 19de61de4..9e16c3115 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenCoordinator.swift @@ -28,7 +28,7 @@ enum SecureBackupRecoveryKeyScreenCoordinatorAction { case recoverySetUp case recoveryChanged case recoveryFixed - case showResetKeyInfo + case resetEncryption } final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol { @@ -65,8 +65,8 @@ final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol { case .unknown: fatalError() } - case .showResetKeyInfo: - self.actionsSubject.send(.showResetKeyInfo) + case .resetEncryption: + self.actionsSubject.send(.resetEncryption) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenModels.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenModels.swift index 3bccf26ce..08488c5ef 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenModels.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenModels.swift @@ -19,7 +19,7 @@ import Foundation enum SecureBackupRecoveryKeyScreenViewModelAction { case done(mode: SecureBackupRecoveryKeyScreenViewMode) case cancel - case showResetKeyInfo + case resetEncryption } enum SecureBackupRecoveryKeyScreenViewMode { @@ -90,7 +90,7 @@ enum SecureBackupRecoveryKeyScreenViewAction { case copyKey case keySaved case confirmKey - case resetKey + case resetEncryption case done case cancel } diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenViewModel.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenViewModel.swift index c8aa406e4..c507f761a 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/SecureBackupRecoveryKeyScreenViewModel.swift @@ -40,13 +40,12 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM secureBackupController.recoveryState .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak userIndicatorController] state in - let loadingIndicatorIdentifier = "SecureBackupRecoveryKeyScreenLoading" + .sink(receiveValue: { [weak self] state in switch state { case .settingUp: - userIndicatorController?.submitIndicator(.init(id: loadingIndicatorIdentifier, type: .modal, title: L10n.commonLoading, persistent: true)) + self?.showLoadingIndicator() default: - userIndicatorController?.retractIndicatorWithId(loadingIndicatorIdentifier) + self?.hideLoadingIndicator() } }) .store(in: &cancellables) @@ -67,6 +66,8 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM MXLog.error("Failed generating recovery key with error: \(error)") state.bindings.alertInfo = .init(id: .init()) } + + hideLoadingIndicator() } case .copyKey: UIPasteboard.general.string = state.recoveryKey @@ -76,8 +77,7 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM state.doneButtonEnabled = true case .confirmKey: Task { - let loadingIndicatorIdentifier = "SecureBackupRecoveryKeyScreen" - userIndicatorController.submitIndicator(.init(id: loadingIndicatorIdentifier, type: .modal, title: L10n.commonLoading, persistent: true)) + showLoadingIndicator() switch await secureBackupController.confirmRecoveryKey(state.bindings.confirmationRecoveryKey) { case .success: @@ -87,7 +87,7 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM state.bindings.alertInfo = .init(id: .init()) } - userIndicatorController.retractIndicatorWithId(loadingIndicatorIdentifier) + hideLoadingIndicator() } case .cancel: actionsSubject.send(.cancel) @@ -100,10 +100,23 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM guard let self else { return } actionsSubject.send(.done(mode: context.viewState.mode)) })) - case .resetKey: - actionsSubject.send(.showResetKeyInfo) + case .resetEncryption: + actionsSubject.send(.resetEncryption) } } + + private static let loadingIndicatorIdentifier = "\(SecureBackupRecoveryKeyScreenViewModel.self)-Loading" + + private func showLoadingIndicator() { + userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, + type: .modal, + title: L10n.commonLoading, + persistent: true)) + } + + private func hideLoadingIndicator() { + userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) + } } extension SecureBackupRecoveryState { diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift index 2d880ff27..06c2c6f99 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift @@ -101,7 +101,7 @@ struct SecureBackupRecoveryKeyScreen: View { .disabled(context.confirmationRecoveryKey.isEmpty) Button { - context.send(viewAction: .resetKey) + context.send(viewAction: .resetEncryption) } label: { Text(L10n.screenIdentityConfirmationCreateNewRecoveryKey) .padding(.vertical, 14) @@ -205,9 +205,6 @@ struct SecureBackupRecoveryKeyScreen: View { .font(.compound.bodySMSemibold) SecureField(L10n.screenRecoveryKeyConfirmKeyPlaceholder, text: $context.confirmationRecoveryKey) - .textContentType(.password) // Not ideal but stops random suggestions - .autocapitalization(.none) - .disableAutocorrection(true) .frame(maxWidth: .infinity) .padding() .background(Color.compound.bgSubtleSecondaryLevel0) diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift index f1dd762a8..c6eb5b20e 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift @@ -19,20 +19,30 @@ import SwiftUI struct SecureBackupScreenCoordinatorParameters { let appSettings: AppSettings - let secureBackupController: SecureBackupControllerProtocol + let clientProxy: ClientProxyProtocol weak var navigationStackCoordinator: NavigationStackCoordinator? let userIndicatorController: UserIndicatorControllerProtocol } +enum SecureBackupScreenCoordinatorAction { + case requestOIDCAuthorisation(URL) +} + final class SecureBackupScreenCoordinator: CoordinatorProtocol { private let parameters: SecureBackupScreenCoordinatorParameters private var viewModel: SecureBackupScreenViewModelProtocol + private var cancellables = Set() + private let actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + init(parameters: SecureBackupScreenCoordinatorParameters) { self.parameters = parameters - viewModel = SecureBackupScreenViewModel(secureBackupController: parameters.secureBackupController, + viewModel = SecureBackupScreenViewModel(secureBackupController: parameters.clientProxy.secureBackupController, userIndicatorController: parameters.userIndicatorController, chatBackupDetailsURL: parameters.appSettings.chatBackupDetailsURL) } @@ -43,9 +53,9 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol { switch action { case .recoveryKey: - let navigationStackCoordinator = NavigationStackCoordinator() + let recoveryNavigationStackCoordinator = NavigationStackCoordinator() - let recoveryKeyCoordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: .init(secureBackupController: parameters.secureBackupController, + let recoveryKeyCoordinator = SecureBackupRecoveryKeyScreenCoordinator(parameters: .init(secureBackupController: parameters.clientProxy.secureBackupController, userIndicatorController: parameters.userIndicatorController, isModallyPresented: true)) @@ -63,19 +73,19 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol { case .recoveryFixed: showSuccessIndicator(title: L10n.screenRecoveryKeyConfirmSuccess) parameters.navigationStackCoordinator?.setSheetCoordinator(nil) - case .showResetKeyInfo: - showResetRecoveryKeyScreen(navigationStackCoordinator: navigationStackCoordinator) + case .resetEncryption: + showEncryptionReset(recoveryNavigationStackCoordinator: recoveryNavigationStackCoordinator) } } .store(in: &cancellables) - navigationStackCoordinator.setRootCoordinator(recoveryKeyCoordinator, animated: true) + recoveryNavigationStackCoordinator.setRootCoordinator(recoveryKeyCoordinator, animated: true) - parameters.navigationStackCoordinator?.setSheetCoordinator(navigationStackCoordinator) + parameters.navigationStackCoordinator?.setSheetCoordinator(recoveryNavigationStackCoordinator) case .keyBackup: let navigationStackCoordinator = NavigationStackCoordinator() - let keyBackupCoordinator = SecureBackupKeyBackupScreenCoordinator(parameters: .init(secureBackupController: parameters.secureBackupController, + let keyBackupCoordinator = SecureBackupKeyBackupScreenCoordinator(parameters: .init(secureBackupController: parameters.clientProxy.secureBackupController, userIndicatorController: parameters.userIndicatorController)) keyBackupCoordinator.actions.sink { [weak self] action in @@ -108,15 +118,30 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol { persistent: false)) } - private func showResetRecoveryKeyScreen(navigationStackCoordinator: NavigationStackCoordinator) { - let coordinator = ResetRecoveryKeyScreenCoordinator() - coordinator.actionsPublisher.sink { action in + 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: - navigationStackCoordinator.setSheetCoordinator(nil) + 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) - navigationStackCoordinator.setSheetCoordinator(coordinator) + + resetNavigationStackCoordinator.setRootCoordinator(coordinator) + + recoveryNavigationStackCoordinator.setSheetCoordinator(resetNavigationStackCoordinator) } } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift index 861426352..61a17ad96 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift @@ -42,12 +42,12 @@ final class SettingsScreenCoordinator: CoordinatorProtocol { private var viewModel: SettingsScreenViewModelProtocol private let actionsSubject: PassthroughSubject = .init() - private var cancellables = Set() - var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } + private var cancellables = Set() + // MARK: - Setup init(parameters: SettingsScreenCoordinatorParameters) { diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 845ecf257..95f063c6e 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -883,7 +883,7 @@ class ClientProxy: ClientProxyProtocol { } } - // MARK: - Encryption + // MARK: - Crypto func ed25519Base64() async -> String? { await client.encryption().ed25519Key() @@ -892,6 +892,14 @@ class ClientProxy: ClientProxyProtocol { func curve25519Base64() async -> String? { await client.encryption().curve25519Key() } + + func resetIdentity() async -> Result { + do { + return try await .success(client.encryption().resetIdentity()) + } catch { + return .failure(.sdkError(error)) + } + } } extension ClientProxy: MediaLoaderProtocol { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 70eba486c..62785e59f 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -190,8 +190,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { func recentConversationCounterparts() async -> [UserProfileProxy] - // MARK: - Encryption Info + // MARK: - Crypto func ed25519Base64() async -> String? func curve25519Base64() async -> String? + + func resetIdentity() async -> Result } diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-en-GB.1.png index 130dfc464..9fcaf0bf7 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f21a974ffbabece1a0bf4761e6307c096382fec58c56eb9e6313a2453a1be07 -size 515653 +oid sha256:1c2db75b98f86afcffbba913ef8d728d6a6ab51d3b73314897c1108210d00c45 +size 517693 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-pseudo.1.png index 00150e7d1..bbf759b2f 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2904bd34a9ebd5113aed2fe521a91f552648270a0ecce950353f167dd4306a0d -size 541329 +oid sha256:87469d91baa3ee639d04012ad9afcf049eb8e9c6cd78fb6dadbac5fbcfb481f3 +size 543421 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-en-GB.1.png index b407bf98f..d1778a8a0 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c69e5671c7d50cd1b49590e9daae7ea286ecddde1e88f89181ad48c2456e73d2 -size 314645 +oid sha256:c8f77a8e5c48eb636514ce6f735c79d583c4ffc7b034cdc49484fbff0ec4a882 +size 315619 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-pseudo.1.png index 9c59913de..464eb1063 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreen-iPhone-15-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff8d146eef1c077b5a1eedb7e6872248ea07c06995f443ab3282a10c8e9181d3 -size 319983 +oid sha256:e0f75923ed4fcfb1c774751abe0a5697fbbf71a274a163dce56c1723a1e6ead2 +size 321695 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPad-en-GB.1.png index 5159c1a11..546a8cb34 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPad-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0c76b4c51a368ac5c5bfa4f37355bbfb7f9f8a62f3f97a9b4aead814ed0d242 -size 114226 +oid sha256:3f008b0b91873db9b4102bf8508a78372db9440f577c609e1677837e19561dbb +size 111069 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPad-pseudo.1.png index d47d6d5cb..113986fcf 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPad-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9beba313e377cdf2d2910d576a1f450fa5e1e999c25a5062b663a8b22a45d0cc -size 119634 +oid sha256:668a9c0536b5155b7bf07e6b8fc7de64e94c15868d5efee11cdb771d9cf2175d +size 118743 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPhone-15-en-GB.1.png index e27d857a5..bb8419e8a 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPhone-15-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPhone-15-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20b5be056145918d911a4139b45f46edd1e4a53445e9f75a977dddec3f5da62e -size 76034 +oid sha256:82b98dbc5b90e4f3432d4d311cf98b1f8762e22b7b4437364030633782145f38 +size 73447 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPhone-15-pseudo.1.png index 965a4ec1a..66c4a8b35 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPhone-15-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_analyticsPromptScreenCheckmarkItem-iPhone-15-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b42bce7f01ea1be7410870009a4cb7b80b8ef7fe011cd4bd631b610a530645a2 -size 90993 +oid sha256:3701ce1293f61e7b35abfe35d13f5860c1a87136ecaa3d3406e8d0dd117a6b14 +size 87369 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPad-en-GB.1.png new file mode 100644 index 000000000..d8b0fe30f --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97f681fac8805300e66945b3b597b55cf79dbe10aa3aca33a3f3405f981ae3bd +size 98889 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPad-pseudo.1.png new file mode 100644 index 000000000..38ec7ba5e --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:614d8e886f6bd587cadaf1133a96647ede185615cf6fe4b33a75e9138f7b475c +size 113456 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..4d9b8ee19 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c869098036009618cfd8f5e6491be8f3af6c92d60c2e8d3edc158767a671dfd1 +size 57235 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..1e3ada23d --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetPasswordScreen-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cd756e4bb99afbc90d2fb50fa17c766412698c768d915bd3ee828d5564f38ba +size 73637 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPad-en-GB.1.png new file mode 100644 index 000000000..50304ac0c --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e125593fee18c805f9a4e44737266b17fb7673160a1da100b5b9379e6e3cb50b +size 168133 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPad-pseudo.1.png new file mode 100644 index 000000000..f5aeaabd7 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:434146f53746a7a1bffd6483f3eb47e79bf38b1cbce86de68921391c42ac6817 +size 238287 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..bdc484c59 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:315e9644456d7221df9953dc73ec9158fb2eaa0427898155c5d79066ba7ee0ee +size 124373 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..f086b7cce --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_encryptionResetScreen-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93549b9921c66b937a191ff6e6dccd49f76cafb7f1e62053d1c2b4fa53d5f7b1 +size 164940 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-en-GB.1.png index 3013fe7d5..9bd9deb9b 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c94b4d8994a63360474b918a7461b406107e327c2cf18809f1d211da7e5f1f81 -size 92000 +oid sha256:bf60b748aa8c48aad659c3567c814cc1f601c194fbafcfffeed37fe6f3594e40 +size 108900 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-pseudo.1.png index e6493445e..99fe3cac3 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bd6f0eff9279d3fedceb20af31fd32c70553c5bed46fad1c2ebcecfd171017a -size 100481 +oid sha256:080f9960d7ca7e83a183050cc663c50635a7a6260bcfd808cf7607680cbf7ae4 +size 123013 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-en-GB.1.png index 636d12824..7fa28b5f8 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-en-GB.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a204e019bcac509fd8a24e31d9f7b0c35aac8e3c29aae90ebf89907fbf9a4f9 -size 49920 +oid sha256:797ad10083353eeb2362f882ff1072a213dde5c879511e5854c4aa415f68d35e +size 64174 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-pseudo.1.png index 9e19fd405..f17402a1e 100644 --- a/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-pseudo.1.png +++ b/PreviewTests/__Snapshots__/PreviewTests/test_identityConfirmationScreen-iPhone-15-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12573f8390f7371d346962d7d411b617a701028052e828c4c8158d530bc40bdc -size 66502 +oid sha256:e0d54f1544f9cd0ffc14183a8baff1317fd0c793ca94c7e88e8a0a2d02816b36 +size 81854 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPad-en-GB.1.png deleted file mode 100644 index 4a4b62bde..000000000 --- a/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPad-en-GB.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e84dbf93c9139a5bb20863137b2736db81fb356298cb207b35da3313ed2b3c6 -size 142887 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPad-pseudo.1.png deleted file mode 100644 index 7ed7025bc..000000000 --- a/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPad-pseudo.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:baab0084096811ea0d2aecb19ac51abf64fc286bc4299506686b79350acf19f6 -size 183006 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPhone-15-en-GB.1.png deleted file mode 100644 index 4de75b6f7..000000000 --- a/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPhone-15-en-GB.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:edbce917e9bce0e4dd1f64c88b8c15b7e8a08975470e4b01c7f121ae27d3252e -size 98702 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPhone-15-pseudo.1.png deleted file mode 100644 index 7cf4572a2..000000000 --- a/PreviewTests/__Snapshots__/PreviewTests/test_resetRecoveryKeyScreen-iPhone-15-pseudo.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8da5536587856a1c615648e238a45638b41957784c6c3981359d9bfb1cea5995 -size 148117 diff --git a/project.yml b/project.yml index e508b3147..8b3ec05f9 100644 --- a/project.yml +++ b/project.yml @@ -60,7 +60,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 1.0.29 + exactVersion: 1.0.30 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios