Key backup returns (#1951)

* Converge on UserSessionFlowCoordinator logout confirmation

* Add logout confirmation screen strings to untranslated.

* Fix chat backup learn more URl fragment.

* Implement logout flows that check recovery and key backup for the last session

* Move logout confirmation screen strings to localazy

* Change encrypted timeline item copy to "Waiting for decryption key"

* Use different encrypted history banner based on key backup states

* Introduce a SettingsFlowCoordinator and implement navigation directly to the secure backup screen from the logout flows.

* Fix **mocked** secure backup controller flows

* Simplify encrypted history banner logic

* Address PR comments
This commit is contained in:
Stefan Ceriu
2023-10-24 18:38:41 +03:00
committed by GitHub
parent 082df683ee
commit 2f5ffd43f8
34 changed files with 806 additions and 111 deletions

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
@@ -93,6 +93,7 @@
1795EA6A6C4942CAE0459DF0 /* SecureBackupKeyBackupScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B612853BFB68373249777B /* SecureBackupKeyBackupScreenViewModel.swift */; };
17BC15DA08A52587466698C5 /* RoomMessageEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */; };
1830E5431DB426E2F3660D58 /* NotificationSettingsEditScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F52419AEEDA2C006CB7181 /* NotificationSettingsEditScreenUITests.swift */; };
184D68B82AE7A01400141160 /* SettingsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184D68B72AE7A01400141160 /* SettingsFlowCoordinator.swift */; };
18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; };
1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */; };
19DED23340D0855B59693ED2 /* VoiceMessageRecorderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45C9EAA86423D7D3126DE4F /* VoiceMessageRecorderProtocol.swift */; };
@@ -326,6 +327,7 @@
5992EF10AA157EBD97D88910 /* AudioRecorderState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6569593FA36B22259E806A67 /* AudioRecorderState.swift */; };
5995C63B1C61DE1373AA2BCE /* ComposerToolbarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */; };
59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2245243369B99216C7D84E /* ImageCache.swift */; };
5AE6404C4FD4848ACCFF9EDC /* SecureBackupLogoutConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */; };
5B2D1210B40570D87B11BD3B /* ThreadDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA3F8E905DF50BF22ECC18F /* ThreadDecorator.swift */; };
5B6E5AD224509E6C0B520D6E /* RoomMemberDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDF49CEBC0DFC59C308335F /* RoomMemberDetailsScreenViewModelProtocol.swift */; };
5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */; };
@@ -541,6 +543,7 @@
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
92133B170A1F917685E9FF78 /* OnboardingScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D168471461717AF5689F64B /* OnboardingScreenUITests.swift */; };
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */; };
92D9088B901CEBB1A99ECA4E /* RoomMemberProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */; };
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; };
93A549135E6C027A0D823BFE /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 593FBBF394712F2963E98A0B /* DTCoreText */; };
@@ -689,6 +692,7 @@
B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */; };
B5E455C9689EA600EDB3E9E0 /* NavigationRootCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA28F29C9F93E93CC3C2C715 /* NavigationRootCoordinator.swift */; };
B6048166B4AA4CEFEA9B77A6 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; };
B6064D82FCDCB829601C1F59 /* SecureBackupLogoutConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEE10AB666891E6A675E5E /* SecureBackupLogoutConfirmationScreen.swift */; };
B64C9BCE61E77D578D40D689 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 232F7D3C19F1FEF0E0450110 /* MatrixRustSDK */; };
B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD6E621CC5E6D4830D96D2D /* MockMediaProvider.swift */; };
B66757D0254843162595B25D /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; };
@@ -790,7 +794,9 @@
CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */; };
CFEC53440C572CEEABC4A6A0 /* ElementXAttributeScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */; };
D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; };
D050D7756E92CA061ED0ABF0 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */; };
D0550B8E0AE2C0CDBE52C88F /* MediaPlayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE87C931165F5E201CACBB87 /* MediaPlayerProtocol.swift */; };
D0A965852D6C04138FA55181 /* SecureBackupLogoutConfirmationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */; };
D12F440F7973F1489F61389D /* NotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */; };
D181AC8FF236B7F91C0A8C28 /* MapTiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AA3F4B285570805CB0CCDD /* MapTiler.swift */; };
D19A748E95E2FAB2940570F0 /* CallScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4103AB4340F2974D690A12A /* CallScreen.swift */; };
@@ -883,6 +889,7 @@
EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */; };
EAC6FE2CD4F50A43068ADCD8 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
EAF2B3E6C6AEC4AD3A8BD454 /* RoomMemberDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */; };
EB87DF90CF6F8D5D12404C6E /* SecureBackupLogoutConfirmationScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */; };
EB88DBD77221E2CFE463018C /* NSE.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0D8F620C8B314840D8602E3F /* NSE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
EBDB339A7C127F068B6E52E5 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A634D8DD1E10D858CF7995D /* VoiceMessageRecordingView.swift */; };
EBE13FAB4E29738AC41BD3E5 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; };
@@ -918,6 +925,7 @@
F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */; };
F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */; };
F40B097470D3110DFDB1FAAA /* LegalInformationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */; };
F421FD5979EF53C8204BDC77 /* SecureBackupLogoutConfirmationScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC09F30B0E1010951952BDC /* SecureBackupLogoutConfirmationScreenUITests.swift */; };
F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */; };
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
F519DE17A3A0F760307B2E6D /* InviteUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */; };
@@ -1079,7 +1087,7 @@
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = "<group>"; };
12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = "<group>"; };
12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = "<group>"; };
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
@@ -1095,6 +1103,7 @@
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = "<group>"; };
1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfigurationTests.swift; sourceTree = "<group>"; };
184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = "<group>"; };
184D68B72AE7A01400141160 /* SettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFlowCoordinator.swift; sourceTree = "<group>"; };
1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReadReceiptsView.swift; sourceTree = "<group>"; };
18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxy.swift; sourceTree = "<group>"; };
18FE0CDF1FFA92EA7EE17B0B /* RoomTimelineControllerFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactoryProtocol.swift; sourceTree = "<group>"; };
@@ -1115,6 +1124,7 @@
1B8E176484A89BAC389D4076 /* RoomMembersListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreen.swift; sourceTree = "<group>"; };
1B927CF5EF7FCCDA5EDC474B /* NotificationItemProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxyProtocol.swift; sourceTree = "<group>"; };
1C21A715237F2B6D6E80998C /* SecureBackupControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerProtocol.swift; sourceTree = "<group>"; };
1CC09F30B0E1010951952BDC /* SecureBackupLogoutConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenUITests.swift; sourceTree = "<group>"; };
1CC575D1895FA62591451A93 /* RoomMemberDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreen.swift; sourceTree = "<group>"; };
1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyProtocol.swift; sourceTree = "<group>"; };
1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
@@ -1205,6 +1215,7 @@
37A63A59BFDDC494B1C20119 /* CallScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenViewModel.swift; sourceTree = "<group>"; };
37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringTests.swift; sourceTree = "<group>"; };
37E727F7E0BCE8A0BBFD33FF /* OnboardingScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreenCoordinator.swift; sourceTree = "<group>"; };
37FEE10AB666891E6A675E5E /* SecureBackupLogoutConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreen.swift; sourceTree = "<group>"; };
382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyMock.swift; sourceTree = "<group>"; };
38345442415E07A931197C55 /* AppLockScreenPINKeypad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenPINKeypad.swift; sourceTree = "<group>"; };
38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreen.swift; sourceTree = "<group>"; };
@@ -1413,6 +1424,7 @@
7475C5AE20BA896930907EA8 /* AudioRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRoomTimelineItemContent.swift; sourceTree = "<group>"; };
748AE77AC3B0A01223033B87 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenCoordinator.swift; sourceTree = "<group>"; };
74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signposter.swift; sourceTree = "<group>"; };
75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineView.swift; sourceTree = "<group>"; };
75910F5A36EA8FF9BAD08D18 /* MigrationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenUITests.swift; sourceTree = "<group>"; };
@@ -1435,6 +1447,7 @@
7CA3F8E905DF50BF22ECC18F /* ThreadDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDecorator.swift; sourceTree = "<group>"; };
7D0CBC76C80E04345E11F2DB /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactoryProtocol.swift; sourceTree = "<group>"; };
7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModel.swift; sourceTree = "<group>"; };
7DDBF99755A9008CF8C8499E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
7DDF49CEBC0DFC59C308335F /* RoomMemberDetailsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
7E492690C8B27A892C194CC4 /* AdvancedSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -1455,6 +1468,7 @@
840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = "<group>"; };
845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersViewModelTests.swift; sourceTree = "<group>"; };
84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = "<group>"; };
848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelTests.swift; sourceTree = "<group>"; };
84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreen.swift; sourceTree = "<group>"; };
84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModel.swift; sourceTree = "<group>"; };
84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunnerTests.swift; sourceTree = "<group>"; };
@@ -1489,7 +1503,7 @@
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
@@ -1624,7 +1638,7 @@
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; };
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = "<group>"; };
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = "<group>"; };
B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@@ -1727,7 +1741,7 @@
CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = "<group>"; };
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = "<group>"; };
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
@@ -1777,6 +1791,7 @@
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = "<group>"; };
DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = "<group>"; };
DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenModels.swift; sourceTree = "<group>"; };
DD97F9661ABF08CE002054A2 /* AppLockServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceTests.swift; sourceTree = "<group>"; };
DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenUITests.swift; sourceTree = "<group>"; };
DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = "<group>"; };
@@ -1784,6 +1799,7 @@
DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = "<group>"; };
E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerAuthorization.swift; sourceTree = "<group>"; };
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = "<group>"; };
E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; };
E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarSize.swift; sourceTree = "<group>"; };
@@ -1827,7 +1843,7 @@
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = "<group>"; };
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
@@ -1842,7 +1858,7 @@
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = "<group>"; };
F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = "<group>"; };
F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = "<group>"; };
F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = "<group>"; };
F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = "<group>"; };
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = "<group>"; };
F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = "<group>"; };
@@ -2279,6 +2295,7 @@
isa = PBXGroup;
children = (
B1FD4FD6CEB987AE274AEEE5 /* SecureBackupKeyBackupScreen */,
63E514D74481A3D9556DFFC3 /* SecureBackupLogoutConfirmationScreen */,
6E8F16377AD462BBD4951271 /* SecureBackupRecoveryKeyScreen */,
3B4C46F36A42B42C4EB14933 /* SecureBackupScreen */,
);
@@ -2901,6 +2918,7 @@
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */,
E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */,
184D68B72AE7A01400141160 /* SettingsFlowCoordinator.swift */,
);
path = FlowCoordinators;
sourceTree = "<group>";
@@ -2954,6 +2972,18 @@
path = View;
sourceTree = "<group>";
};
63E514D74481A3D9556DFFC3 /* SecureBackupLogoutConfirmationScreen */ = {
isa = PBXGroup;
children = (
E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */,
DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */,
7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */,
74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */,
FB4987F1C660F2085258866B /* View */,
);
path = SecureBackupLogoutConfirmationScreen;
sourceTree = "<group>";
};
669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */ = {
isa = PBXGroup;
children = (
@@ -3179,6 +3209,7 @@
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */,
AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */,
2E88534A39781D76487D59DF /* SecureBackupKeyBackupScreenViewModelTests.swift */,
848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */,
C0FF08D0BD7D0B4B6877AB7D /* SecureBackupRecoveryKeyScreenViewModelTests.swift */,
40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */,
277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */,
@@ -3645,6 +3676,7 @@
66901977F6469D03C333DF32 /* RoomNotificationSettingsScreenUITests.swift */,
086B997409328F091EBA43CE /* RoomScreenUITests.swift */,
58DCB219D7B7B0299358FF81 /* SecureBackupKeyBackupScreenUITests.swift */,
1CC09F30B0E1010951952BDC /* SecureBackupLogoutConfirmationScreenUITests.swift */,
FDBA358C79F0DCBC4FA14A88 /* SecureBackupRecoveryKeyScreenUITests.swift */,
91831D7042EADD0CC2B5EC36 /* SecureBackupScreenUITests.swift */,
DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */,
@@ -4544,6 +4576,14 @@
path = Common;
sourceTree = "<group>";
};
FB4987F1C660F2085258866B /* View */ = {
isa = PBXGroup;
children = (
37FEE10AB666891E6A675E5E /* SecureBackupLogoutConfirmationScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
FCDF06BDB123505F0334B4F9 /* Timeline */ = {
isa = PBXGroup;
children = (
@@ -5136,6 +5176,7 @@
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */,
CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */,
7691233E3572A9173FD96CB3 /* SecureBackupKeyBackupScreenViewModelTests.swift in Sources */,
EB87DF90CF6F8D5D12404C6E /* SecureBackupLogoutConfirmationScreenViewModelTests.swift in Sources */,
06B31F84CE52A7A7C271267C /* SecureBackupRecoveryKeyScreenViewModelTests.swift in Sources */,
1B8E30B35BF8F541C1318F19 /* SecureBackupScreenViewModelTests.swift in Sources */,
53A55748D5F587C9061F98BF /* ServerConfigurationScreenViewStateTests.swift in Sources */,
@@ -5351,6 +5392,7 @@
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */,
1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */,
FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */,
184D68B82AE7A01400141160 /* SettingsFlowCoordinator.swift in Sources */,
5D27B6537591471A42C89027 /* EmoteRoomTimelineItem.swift in Sources */,
8B41D0357B91CD3B6F6A3BCA /* EmoteRoomTimelineItemContent.swift in Sources */,
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */,
@@ -5656,6 +5698,11 @@
B7888FC1E1DEF816D175C8D6 /* SecureBackupKeyBackupScreenModels.swift in Sources */,
1795EA6A6C4942CAE0459DF0 /* SecureBackupKeyBackupScreenViewModel.swift in Sources */,
A4C29D373986AFE4559696D5 /* SecureBackupKeyBackupScreenViewModelProtocol.swift in Sources */,
B6064D82FCDCB829601C1F59 /* SecureBackupLogoutConfirmationScreen.swift in Sources */,
5AE6404C4FD4848ACCFF9EDC /* SecureBackupLogoutConfirmationScreenCoordinator.swift in Sources */,
D0A965852D6C04138FA55181 /* SecureBackupLogoutConfirmationScreenModels.swift in Sources */,
92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */,
D050D7756E92CA061ED0ABF0 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift in Sources */,
FA71CD334F2D2289BEF0D749 /* SecureBackupRecoveryKeyScreen.swift in Sources */,
B1387648C6F71F1B98244803 /* SecureBackupRecoveryKeyScreenCoordinator.swift in Sources */,
8AA84EF202F2EFC8453A97BD /* SecureBackupRecoveryKeyScreenModels.swift in Sources */,
@@ -5874,6 +5921,7 @@
06AA515C7053FD7E17A5CF81 /* RoomNotificationSettingsScreenUITests.swift in Sources */,
2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */,
A743841F91B62B0E56217B04 /* SecureBackupKeyBackupScreenUITests.swift in Sources */,
F421FD5979EF53C8204BDC77 /* SecureBackupLogoutConfirmationScreenUITests.swift in Sources */,
FC4F6BA083A64840B38CE269 /* SecureBackupRecoveryKeyScreenUITests.swift in Sources */,
8C42B5B1642D189C362A5EDF /* SecureBackupScreenUITests.swift in Sources */,
A1D4033881320C9EB88196E6 /* ServerConfirmationScreenUITests.swift in Sources */,

View File

@@ -438,7 +438,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
guard let self else { return }
switch action {
case .signOut:
case .logout:
stateMachine.processEvent(.signOut(isSoft: false))
case .clearCache:
stateMachine.processEvent(.clearCache)

View File

@@ -118,7 +118,7 @@ final class AppSettings {
/// An email address that should be used for support requests.
let supportEmailAddress = "support@element.io"
// A URL where users can go read more about the chat backup.
let chatBackupDetailsURL: URL = "https://element.io/help#encryption"
let chatBackupDetailsURL: URL = "https://element.io/help#encryption5"
// MARK: - Security

View File

@@ -24,6 +24,8 @@ enum AppRoute: Equatable {
case roomMemberDetails(userID: String)
case invites
case genericCallLink(url: URL)
case settings
case chatBackupSettings
}
struct AppRouteURLParser {

View File

@@ -106,9 +106,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
MXLog.error("[RoomFlowCoordinator] Failed to get member: RoomProxy is nil")
}
}
case .invites:
break
case .genericCallLink, .oidcCallback:
case .invites, .genericCallLink, .oidcCallback, .settings, .chatBackupSettings:
break
}
}
@@ -347,7 +345,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider,
mediaPlayerProvider: mediaPlayerProvider,
voiceMessageMediaManager: userSession.voiceMessageMediaManager)
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
secureBackupController: userSession.clientProxy.secureBackupController)
self.timelineController = timelineController
analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace)

View File

@@ -0,0 +1,135 @@
//
// Copyright 2023 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 Foundation
enum SettingsFlowCoordinatorAction {
case presentedSettings
case dismissedSettings
case runLogoutFlow
case clearCache
}
struct SettingsFlowCoordinatorParameters {
let userSession: UserSessionProtocol
let appLockService: AppLockServiceProtocol
let bugReportService: BugReportServiceProtocol
let notificationSettings: NotificationSettingsProxyProtocol
let secureBackupController: SecureBackupControllerProtocol
let appSettings: AppSettings
let navigationSplitCoordinator: NavigationSplitCoordinator
}
class SettingsFlowCoordinator: FlowCoordinatorProtocol {
private let parameters: SettingsFlowCoordinatorParameters
private var navigationStackCoordinator: NavigationStackCoordinator!
private var userIndicatorController: UserIndicatorControllerProtocol!
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init()
var actions: AnyPublisher<SettingsFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: SettingsFlowCoordinatorParameters) {
self.parameters = parameters
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
switch appRoute {
case .settings:
presentSettingsScreen(animated: animated)
case .chatBackupSettings:
if navigationStackCoordinator == nil {
presentSettingsScreen(animated: animated)
}
// The navigation stack doesn't like it if the root and the push happen
// on the same loop run
DispatchQueue.main.async {
self.presentSecureBackupScreen(animated: animated)
}
default:
break
}
}
func clearRoute(animated: Bool) { }
// MARK: - Private
private func presentSettingsScreen(animated: Bool) {
navigationStackCoordinator = NavigationStackCoordinator()
userIndicatorController = UserIndicatorController(rootCoordinator: navigationStackCoordinator)
let parameters = SettingsScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: userIndicatorController,
userSession: parameters.userSession,
appLockService: parameters.appLockService,
bugReportService: parameters.bugReportService,
notificationSettings: parameters.userSession.clientProxy.notificationSettings,
secureBackupController: parameters.userSession.clientProxy.secureBackupController,
appSettings: parameters.appSettings)
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: parameters)
settingsScreenCoordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss:
self.parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
case .logout:
self.parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
// The settings sheet needs to be dismissed before the alert can be shown
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.actionsSubject.send(.runLogoutFlow)
}
case .clearCache:
actionsSubject.send(.clearCache)
case .secureBackup:
presentSecureBackupScreen(animated: true)
}
}
.store(in: &cancellables)
navigationStackCoordinator.setRootCoordinator(settingsScreenCoordinator, animated: animated)
self.parameters.navigationSplitCoordinator.setSheetCoordinator(userIndicatorController) { [weak self] in
guard let self else { return }
navigationStackCoordinator = nil
userIndicatorController = nil
actionsSubject.send(.dismissedSettings)
}
actionsSubject.send(.presentedSettings)
}
private func presentSecureBackupScreen(animated: Bool) {
let coordinator = SecureBackupScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
secureBackupController: parameters.userSession.clientProxy.secureBackupController,
navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: userIndicatorController))
navigationStackCoordinator.push(coordinator, animated: animated)
}
}

View File

@@ -18,7 +18,7 @@ import Combine
import SwiftUI
enum UserSessionFlowCoordinatorAction {
case signOut
case logout
case clearCache
}
@@ -34,6 +34,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let stateMachine: UserSessionFlowCoordinatorStateMachine
private let roomFlowCoordinator: RoomFlowCoordinator
private let settingsFlowCoordinator: SettingsFlowCoordinator
private var cancellables = Set<AnyCancellable>()
private var migrationCancellable: AnyCancellable?
@@ -76,11 +77,20 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
appSettings: appSettings,
analytics: analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController)
settingsFlowCoordinator = SettingsFlowCoordinator(parameters: .init(userSession: userSession,
appLockService: appLockService,
bugReportService: bugReportService,
notificationSettings: userSession.clientProxy.notificationSettings,
secureBackupController: userSession.clientProxy.secureBackupController,
appSettings: appSettings,
navigationSplitCoordinator: navigationSplitCoordinator))
setupStateMachine()
roomFlowCoordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentedRoom(let roomID):
analytics.signpost.beginRoomFlow(roomID)
@@ -93,6 +103,22 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
}
.store(in: &cancellables)
settingsFlowCoordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentedSettings:
stateMachine.processEvent(.showSettingsScreen)
case .dismissedSettings:
stateMachine.processEvent(.dismissedSettingsScreen)
case .runLogoutFlow:
runLogoutFlow()
case .clearCache:
actionsSubject.send(.clearCache)
}
}
.store(in: &cancellables)
}
func start() {
@@ -131,6 +157,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
self.navigationSplitCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)), animated: animated)
case .oidcCallback:
break
case .settings, .chatBackupSettings:
settingsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
}
}
}
@@ -193,7 +221,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
break
case (.roomList, .showSettingsScreen, .settingsScreen):
presentSettingsScreen(animated: animated)
break
case (.settingsScreen, .dismissedSettingsScreen, .roomList):
break
@@ -211,7 +239,12 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
presentInvitesList(animated: animated)
case (.invitesScreen, .showInvitesScreen, .invitesScreen):
break
case (.invitesScreen, .closedInvitesScreen, .roomList):
case (.invitesScreen, .dismissedInvitesScreen, .roomList):
break
case (.roomList, .showLogoutConfirmationScreen, .logoutConfirmationScreen):
presentSecureBackupConfirmationScreen()
case (.logoutConfirmationScreen, .dismissedLogoutConfirmationScreen, .roomList):
break
default:
@@ -288,15 +321,15 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
roomFlowCoordinator.handleAppRoute(.roomList, animated: true)
}
case .presentSettingsScreen:
stateMachine.processEvent(.showSettingsScreen)
settingsFlowCoordinator.handleAppRoute(.settings, animated: true)
case .presentFeedbackScreen:
stateMachine.processEvent(.feedbackScreen)
case .presentSessionVerificationScreen:
stateMachine.processEvent(.showSessionVerificationScreen)
case .presentStartChatScreen:
stateMachine.processEvent(.showStartChatScreen)
case .signOut:
actionsSubject.send(.signOut)
case .logout:
runLogoutFlow()
case .presentInvitesScreen:
stateMachine.processEvent(.showInvitesScreen)
}
@@ -321,44 +354,44 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
}
// MARK: Settings
private func presentSettingsScreen(animated: Bool) {
let settingsNavigationStackCoordinator = NavigationStackCoordinator()
private func runLogoutFlow() {
let secureBackupController = userSession.clientProxy.secureBackupController
let userIndicatorController = UserIndicatorController(rootCoordinator: settingsNavigationStackCoordinator)
let parameters = SettingsScreenCoordinatorParameters(navigationStackCoordinator: settingsNavigationStackCoordinator,
userIndicatorController: userIndicatorController,
userSession: userSession,
appLockService: appLockService,
bugReportService: bugReportService,
notificationSettings: userSession.clientProxy.notificationSettings,
secureBackupController: userSession.clientProxy.secureBackupController,
appSettings: appSettings)
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: parameters)
settingsScreenCoordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss:
navigationSplitCoordinator.setSheetCoordinator(nil)
case .logout:
navigationSplitCoordinator.setSheetCoordinator(nil)
actionsSubject.send(.signOut)
case .clearCache:
actionsSubject.send(.clearCache)
}
}
.store(in: &cancellables)
settingsNavigationStackCoordinator.setRootCoordinator(settingsScreenCoordinator, animated: animated)
navigationSplitCoordinator.setSheetCoordinator(userIndicatorController) { [weak self] in
self?.stateMachine.processEvent(.dismissedSettingsScreen)
guard secureBackupController.isLastSession, appSettings.chatBackupEnabled else {
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
title: L10n.screenSignoutRecoveryDisabledTitle,
message: L10n.screenSignoutRecoveryDisabledSubtitle,
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
self?.actionsSubject.send(.logout)
})
return
}
guard secureBackupController.recoveryKeyState.value == .enabled else {
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
title: L10n.screenSignoutRecoveryDisabledTitle,
message: L10n.screenSignoutRecoveryDisabledSubtitle,
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
self?.actionsSubject.send(.logout)
}, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in
self?.settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
})
return
}
guard secureBackupController.keyBackupState.value == .enabled else {
ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(),
title: L10n.screenSignoutKeyBackupDisabledTitle,
message: L10n.screenSignoutKeyBackupDisabledSubtitle,
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
self?.actionsSubject.send(.logout)
}, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in
self?.settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
})
return
}
presentSecureBackupConfirmationScreen()
}
// MARK: Session verification
@@ -457,7 +490,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
.store(in: &cancellables)
sidebarNavigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
self?.stateMachine.processEvent(.closedInvitesScreen)
self?.stateMachine.processEvent(.dismissedInvitesScreen)
}
}
@@ -479,4 +512,28 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
navigationSplitCoordinator.setSheetCoordinator(callScreenCoordinator, animated: true)
}
// MARK: Secure backup confirmation
private func presentSecureBackupConfirmationScreen() {
let coordinator = SecureBackupLogoutConfirmationScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
networkMonitor: ServiceLocator.shared.networkMonitor))
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .cancel:
navigationSplitCoordinator.setSheetCoordinator(nil)
case .settings:
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
case .logout:
actionsSubject.send(.logout)
}
}
.store(in: &cancellables)
navigationSplitCoordinator.setSheetCoordinator(coordinator, animated: true)
}
}

View File

@@ -46,6 +46,9 @@ class UserSessionFlowCoordinatorStateMachine {
/// Showing invites list screen
case invitesScreen(selectedRoomID: String?)
// Showing the logout flows
case logoutConfirmationScreen(selectedRoomID: String?)
}
struct EventUserInfo {
@@ -100,7 +103,12 @@ class UserSessionFlowCoordinatorStateMachine {
/// Request presentation of the invites screen
case showInvitesScreen
/// The invites screen has been dismissed
case closedInvitesScreen
case dismissedInvitesScreen
/// Logout has been requested and this is the last sesion
case showLogoutConfirmationScreen
/// Logout has been cancelled
case dismissedLogoutConfirmationScreen
}
private let stateMachine: StateMachine<State, Event>
@@ -157,12 +165,17 @@ class UserSessionFlowCoordinatorStateMachine {
case (.showInvitesScreen, .invitesScreen(let selectedRoomID)):
return .invitesScreen(selectedRoomID: selectedRoomID)
case (.closedInvitesScreen, .invitesScreen(let selectedRoomID)):
case (.dismissedInvitesScreen, .invitesScreen(let selectedRoomID)):
return .roomList(selectedRoomID: selectedRoomID)
case (.presentWelcomeScreen, .roomList):
return .welcomeScreen
case (.showLogoutConfirmationScreen, .roomList(let selectedRoomID)):
return .logoutConfirmationScreen(selectedRoomID: selectedRoomID)
case (.dismissedLogoutConfirmationScreen, .logoutConfirmationScreen(let selectedRoomID)):
return .roomList(selectedRoomID: selectedRoomID)
default:
return nil
}

View File

@@ -984,6 +984,24 @@ class MediaPlayerProviderMock: MediaPlayerProviderProtocol {
await detachAllStatesExceptClosure?(exception)
}
}
class NetworkMonitorMock: NetworkMonitorProtocol {
var reachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never> {
get { return underlyingReachabilityPublisher }
set(value) { underlyingReachabilityPublisher = value }
}
var underlyingReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never>!
var isCurrentConnectionExpensive: Bool {
get { return underlyingIsCurrentConnectionExpensive }
set(value) { underlyingIsCurrentConnectionExpensive = value }
}
var underlyingIsCurrentConnectionExpensive: Bool!
var isCurrentConnectionConstrained: Bool {
get { return underlyingIsCurrentConnectionConstrained }
set(value) { underlyingIsCurrentConnectionConstrained = value }
}
var underlyingIsCurrentConnectionConstrained: Bool!
}
class NotificationCenterMock: NotificationCenterProtocol {
//MARK: - post
@@ -2400,6 +2418,11 @@ class SecureBackupControllerMock: SecureBackupControllerProtocol {
set(value) { underlyingKeyBackupState = value }
}
var underlyingKeyBackupState: CurrentValuePublisher<SecureBackupKeyBackupState, Never>!
var isLastSession: Bool {
get { return underlyingIsLastSession }
set(value) { underlyingIsLastSession = value }
}
var underlyingIsLastSession: Bool!
//MARK: - enableBackup
@@ -2473,6 +2496,18 @@ class SecureBackupControllerMock: SecureBackupControllerProtocol {
return confirmRecoveryKeyReturnValue
}
}
//MARK: - waitForKeyBackup
var waitForKeyBackupCallsCount = 0
var waitForKeyBackupCalled: Bool {
return waitForKeyBackupCallsCount > 0
}
var waitForKeyBackupClosure: (() async -> Void)?
func waitForKeyBackup() async {
waitForKeyBackupCallsCount += 1
await waitForKeyBackupClosure?()
}
}
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {

View File

@@ -26,3 +26,6 @@ protocol NetworkMonitorProtocol {
var isCurrentConnectionExpensive: Bool { get }
var isCurrentConnectionConstrained: Bool { get }
}
// sourcery: AutoMockable
extension NetworkMonitorProtocol { }

View File

@@ -34,7 +34,7 @@ enum HomeScreenCoordinatorAction {
case presentSessionVerificationScreen
case presentStartChatScreen
case presentInvitesScreen
case signOut
case logout
}
final class HomeScreenCoordinator: CoordinatorProtocol {
@@ -75,8 +75,8 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.presentSettingsScreen)
case .presentSessionVerificationScreen:
actionsSubject.send(.presentSessionVerificationScreen)
case .signOut:
actionsSubject.send(.signOut)
case .logout:
actionsSubject.send(.logout)
case .presentStartChatScreen:
actionsSubject.send(.presentStartChatScreen)
case .presentInvitesScreen:

View File

@@ -27,13 +27,13 @@ enum HomeScreenViewModelAction {
case presentFeedbackScreen
case presentStartChatScreen
case presentInvitesScreen
case signOut
case logout
}
enum HomeScreenViewUserMenuAction {
case settings
case feedback
case signOut
case logout
}
enum HomeScreenViewAction {

View File

@@ -138,8 +138,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
actionsSubject.send(.presentFeedbackScreen)
case .settings:
actionsSubject.send(.presentSettingsScreen)
case .signOut:
actionsSubject.send(.signOut)
case .logout:
actionsSubject.send(.logout)
}
case .verifySession:
actionsSubject.send(.presentSessionVerificationScreen)

View File

@@ -18,7 +18,6 @@ import Compound
import SwiftUI
struct HomeScreenUserMenuButton: View {
@State private var showingLogoutConfirmation = false
@Environment(\.colorScheme) var colorScheme
@ObservedObject var context: HomeScreenViewModel.Context
@@ -53,7 +52,7 @@ struct HomeScreenUserMenuButton: View {
}
Section {
Button(role: .destructive) {
showingLogoutConfirmation = true
context.send(viewAction: .userMenu(action: .logout))
} label: {
Label(L10n.screenSignoutPreferenceItem, systemImage: "rectangle.portrait.and.arrow.right")
}
@@ -68,15 +67,6 @@ struct HomeScreenUserMenuButton: View {
.overlayBadge(10, isBadged: context.viewState.showUserMenuBadge)
.compositingGroup()
}
.alert(L10n.screenSignoutConfirmationDialogTitle,
isPresented: $showingLogoutConfirmation) {
Button(L10n.screenSignoutConfirmationDialogSubmit,
role: .destructive) {
context.send(viewAction: .userMenu(action: .signOut))
}
} message: {
Text(L10n.screenSignoutConfirmationDialogContent)
}
.accessibilityLabel(L10n.a11yUserMenu)
}
}

View File

@@ -24,7 +24,7 @@ struct EncryptedHistoryRoomTimelineView: View {
var body: some View {
Label {
Text(L10n.screenRoomEncryptedHistoryBanner)
Text(title)
.font(.compound.bodyMDSemibold)
.foregroundColor(.compound.textInfoPrimary)
} icon: {
@@ -43,6 +43,10 @@ struct EncryptedHistoryRoomTimelineView: View {
.padding(.horizontal, 8)
.padding(.vertical, 16)
}
private var title: String {
timelineItem.isSessionVerified ? L10n.screenRoomEncryptedHistoryBanner : L10n.screenRoomEncryptedHistoryBannerUnverified
}
}
private struct EncryptedHistoryLabelStyle: LabelStyle {
@@ -56,7 +60,9 @@ private struct EncryptedHistoryLabelStyle: LabelStyle {
struct EncryptedHistoryRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
let item = EncryptedHistoryRoomTimelineItem(id: .random)
EncryptedHistoryRoomTimelineView(timelineItem: item)
VStack {
EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: true))
EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: false))
}
}
}

View File

@@ -21,8 +21,6 @@ import SwiftUI
struct SecureBackupKeyBackupScreen: View {
@ObservedObject var context: SecureBackupKeyBackupScreenViewModel.Context
@ScaledMetric private var iconSize = 70
var body: some View {
mainContent
.padding()

View File

@@ -0,0 +1,69 @@
//
// 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
struct SecureBackupLogoutConfirmationScreenCoordinatorParameters {
let secureBackupController: SecureBackupControllerProtocol
let networkMonitor: NetworkMonitorProtocol
}
enum SecureBackupLogoutConfirmationScreenCoordinatorAction {
case cancel
case settings
case logout
}
final class SecureBackupLogoutConfirmationScreenCoordinator: CoordinatorProtocol {
private let parameters: SecureBackupLogoutConfirmationScreenCoordinatorParameters
private var viewModel: SecureBackupLogoutConfirmationScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<SecureBackupLogoutConfirmationScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
var actions: AnyPublisher<SecureBackupLogoutConfirmationScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: SecureBackupLogoutConfirmationScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: parameters.secureBackupController,
networkMonitor: parameters.networkMonitor)
}
func start() {
viewModel.actions.sink { [weak self] action in
guard let self else { return }
MXLog.info("Coordinator: received view model action: \(action)")
switch action {
case .cancel:
actionsSubject.send(.cancel)
case .settings:
actionsSubject.send(.settings)
case .logout:
actionsSubject.send(.logout)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(SecureBackupLogoutConfirmationScreen(context: viewModel.context))
}
}

View File

@@ -0,0 +1,39 @@
//
// 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 SecureBackupLogoutConfirmationScreenViewModelAction {
case cancel
case settings
case logout
}
enum SecureBackupLogoutConfirmationScreenViewMode {
case saveRecoveryKey
case backupOngoing
case offline
}
struct SecureBackupLogoutConfirmationScreenViewState: BindableState {
var mode: SecureBackupLogoutConfirmationScreenViewMode
}
enum SecureBackupLogoutConfirmationScreenViewAction {
case cancel
case settings
case logout
}

View File

@@ -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
typealias SecureBackupLogoutConfirmationScreenViewModelType = StateStoreViewModel<SecureBackupLogoutConfirmationScreenViewState, SecureBackupLogoutConfirmationScreenViewAction>
class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmationScreenViewModelType, SecureBackupLogoutConfirmationScreenViewModelProtocol {
private let secureBackupController: SecureBackupControllerProtocol
private let networkMonitor: NetworkMonitorProtocol
@CancellableTask
private var keyUploadWaitingTask: Task<Void, Never>?
private var actionsSubject: PassthroughSubject<SecureBackupLogoutConfirmationScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<SecureBackupLogoutConfirmationScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(secureBackupController: SecureBackupControllerProtocol, networkMonitor: NetworkMonitorProtocol) {
self.secureBackupController = secureBackupController
self.networkMonitor = networkMonitor
super.init(initialViewState: .init(mode: .saveRecoveryKey))
networkMonitor.reachabilityPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] reachability in
guard let self,
state.mode != .saveRecoveryKey else {
return
}
if reachability == .reachable {
state.mode = .backupOngoing
} else {
state.mode = .offline
}
}
.store(in: &cancellables)
}
// MARK: - Public
override func process(viewAction: SecureBackupLogoutConfirmationScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .cancel:
keyUploadWaitingTask = nil
actionsSubject.send(.cancel)
case .settings:
actionsSubject.send(.settings)
case .logout:
attemptLogout()
}
}
// MARK: - Private
private func attemptLogout() {
if state.mode == .saveRecoveryKey {
state.mode = networkMonitor.reachabilityPublisher.value == .reachable ? .backupOngoing : .offline
keyUploadWaitingTask = Task {
await secureBackupController.waitForKeyBackup()
guard !Task.isCancelled else { return }
actionsSubject.send(.logout)
}
} else {
actionsSubject.send(.logout)
}
}
}

View File

@@ -0,0 +1,23 @@
//
// 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 SecureBackupLogoutConfirmationScreenViewModelProtocol {
var actions: AnyPublisher<SecureBackupLogoutConfirmationScreenViewModelAction, Never> { get }
var context: SecureBackupLogoutConfirmationScreenViewModelType.Context { get }
}

View File

@@ -0,0 +1,134 @@
//
// 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 Compound
import SwiftUI
struct SecureBackupLogoutConfirmationScreen: View {
@ObservedObject var context: SecureBackupLogoutConfirmationScreenViewModel.Context
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 16) {
header
content
}
.padding()
}
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.safeAreaInset(edge: .bottom) { footer.padding() }
}
}
@ViewBuilder
private var header: some View {
HeroImage(image: Image(asset: Asset.Images.secureBackupOff))
}
@ViewBuilder
private var content: some View {
Text(title)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
Text(subtitle)
.foregroundColor(.compound.textSecondary)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
if context.viewState.mode == .backupOngoing {
Spacer()
ProgressView()
}
}
@ViewBuilder
private var footer: some View {
if context.viewState.mode == .saveRecoveryKey {
Button {
context.send(viewAction: .settings)
} label: {
Text(L10n.commonSettings)
}
.buttonStyle(.compound(.primary))
}
Button(role: .destructive) {
context.send(viewAction: .logout)
} label: {
Text(L10n.actionSignout)
}
.buttonStyle(.compound(.primary))
}
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
}
var title: String {
switch context.viewState.mode {
case .saveRecoveryKey:
return L10n.screenSignoutSaveRecoveryKeyTitle
case .backupOngoing:
return L10n.screenSignoutKeyBackupOngoingTitle
case .offline:
return L10n.screenSignoutKeyBackupOfflineTitle
}
}
var subtitle: String {
switch context.viewState.mode {
case .saveRecoveryKey:
return L10n.screenSignoutSaveRecoveryKeySubtitle
case .backupOngoing:
return L10n.screenSignoutKeyBackupOngoingSubtitle
case .offline:
return L10n.screenSignoutKeyBackupOfflineSubtitle
}
}
}
// MARK: - Previews
struct SecureBackupLogoutConfirmationScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = buildViewModel()
static var previews: some View {
NavigationStack {
SecureBackupLogoutConfirmationScreen(context: viewModel.context)
}
}
static func buildViewModel() -> SecureBackupLogoutConfirmationScreenViewModelType {
let secureBackupController = SecureBackupControllerMock()
secureBackupController.underlyingKeyBackupState = CurrentValueSubject<SecureBackupKeyBackupState, Never>(.enabled).asCurrentValuePublisher()
let networkMonitor = NetworkMonitorMock()
networkMonitor.underlyingReachabilityPublisher = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable).asCurrentValuePublisher()
return SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: secureBackupController,
networkMonitor: networkMonitor)
}
}

View File

@@ -21,8 +21,6 @@ import SwiftUI
struct SecureBackupRecoveryKeyScreen: View {
@ObservedObject var context: SecureBackupRecoveryKeyScreenViewModel.Context
@ScaledMetric private var iconSize = 70
var body: some View {
mainContent
.padding()

View File

@@ -32,6 +32,7 @@ enum SettingsScreenCoordinatorAction {
case dismiss
case logout
case clearCache
case secureBackup
}
final class SettingsScreenCoordinator: CoordinatorProtocol {
@@ -75,7 +76,7 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
case .sessionVerification:
presentSessionVerificationScreen()
case .secureBackup:
presentSecureBackupScreen()
actionsSubject.send(.secureBackup)
case .accountSessionsList:
presentAccountSessionsListURL()
case .notifications:
@@ -200,15 +201,6 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
}
}
private func presentSecureBackupScreen() {
let coordinator = SecureBackupScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
secureBackupController: parameters.userSession.clientProxy.secureBackupController,
navigationStackCoordinator: parameters.navigationStackCoordinator,
userIndicatorController: parameters.userIndicatorController))
parameters.navigationStackCoordinator?.push(coordinator)
}
private func presentNotificationSettings() {
let notificationParameters = NotificationSettingsScreenCoordinatorParameters(navigationStackCoordinator: parameters.navigationStackCoordinator,
userSession: parameters.userSession,

View File

@@ -19,8 +19,6 @@ import SFSafeSymbols
import SwiftUI
struct SettingsScreen: View {
@State private var showingLogoutConfirmation = false
@ObservedObject var context: SettingsScreenViewModel.Context
var body: some View {
@@ -196,16 +194,9 @@ struct SettingsScreen: View {
ListRow(label: .default(title: L10n.screenSignoutPreferenceItem,
systemIcon: .rectanglePortraitAndArrowRight),
kind: .button {
showingLogoutConfirmation = true
context.send(viewAction: .logout)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.logout)
.alert(L10n.screenSignoutConfirmationDialogTitle, isPresented: $showingLogoutConfirmation) {
Button(L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) {
context.send(viewAction: .logout)
}
} message: {
Text(L10n.screenSignoutConfirmationDialogContent)
}
} footer: {
VStack {
versionText

View File

@@ -29,6 +29,11 @@ class SecureBackupController: SecureBackupControllerProtocol {
keyBackupStateSubject.asCurrentValuePublisher()
}
var isLastSession: Bool {
#warning("FIXME")
return true
}
func enableBackup() async -> Result<Void, SecureBackupControllerError> {
keyBackupStateSubject.send(.enabling)
@@ -76,4 +81,8 @@ class SecureBackupController: SecureBackupControllerProtocol {
return .success(())
}
func waitForKeyBackup() async {
try? await Task.sleep(for: .seconds(5))
}
}

View File

@@ -49,11 +49,15 @@ protocol SecureBackupControllerProtocol {
var keyBackupState: CurrentValuePublisher<SecureBackupKeyBackupState, Never> { get }
var isLastSession: Bool { get }
func enableBackup() async -> Result<Void, SecureBackupControllerError>
func disableBackup() async -> Result<Void, SecureBackupControllerError>
func generateRecoveryKey() async -> Result<String, SecureBackupControllerError>
func confirmRecoveryKey(_ key: String) async -> Result<Void, SecureBackupControllerError>
func waitForKeyBackup() async
}
extension SecureBackupControllerMock {

View File

@@ -17,11 +17,13 @@
import Foundation
struct MockRoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
// swiftlint:disable:next function_parameter_count
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol,
mediaPlayerProvider: MediaPlayerProviderProtocol,
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol) -> RoomTimelineControllerProtocol {
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol {
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
return timelineController

View File

@@ -26,6 +26,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
private let mediaPlayerProvider: MediaPlayerProviderProtocol
private let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
private let appSettings: AppSettings
private let secureBackupController: SecureBackupControllerProtocol
private let serialDispatchQueue: DispatchQueue
private var cancellables = Set<AnyCancellable>()
@@ -48,7 +49,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
mediaProvider: MediaProviderProtocol,
mediaPlayerProvider: MediaPlayerProviderProtocol,
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
appSettings: AppSettings) {
appSettings: AppSettings,
secureBackupController: SecureBackupControllerProtocol) {
self.roomProxy = roomProxy
timelineProvider = roomProxy.timelineProvider
self.timelineItemFactory = timelineItemFactory
@@ -56,6 +58,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
self.mediaPlayerProvider = mediaPlayerProvider
self.voiceMessageMediaManager = voiceMessageMediaManager
self.appSettings = appSettings
self.secureBackupController = secureBackupController
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
timelineProvider
@@ -442,8 +445,14 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
case .event(let eventTimelineItem):
let timelineItem = timelineItemFactory.buildTimelineItem(for: eventTimelineItem)
if timelineItem is EncryptedRoomTimelineItem, isItemInEncryptionHistory(eventTimelineItem) {
return EncryptedHistoryRoomTimelineItem(id: eventTimelineItem.id)
// When backup is enabled just show the timeline items, they will most likely
// resolve eventually. If we don't know the backup state then we assume the session is not verified yet,
// otherwise we just treat it as fully disabled.
if secureBackupController.keyBackupState.value != .enabled {
if timelineItem is EncryptedRoomTimelineItem, isItemInEncryptionHistory(eventTimelineItem) {
return EncryptedHistoryRoomTimelineItem(id: eventTimelineItem.id,
isSessionVerified: secureBackupController.keyBackupState.value != .unknown)
}
}
if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol {

View File

@@ -17,16 +17,19 @@
import Foundation
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
// swiftlint:disable:next function_parameter_count
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol,
mediaPlayerProvider: MediaPlayerProviderProtocol,
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol) -> RoomTimelineControllerProtocol {
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol {
RoomTimelineController(roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider,
mediaPlayerProvider: mediaPlayerProvider,
voiceMessageMediaManager: voiceMessageMediaManager,
appSettings: ServiceLocator.shared.settings)
appSettings: ServiceLocator.shared.settings,
secureBackupController: secureBackupController)
}
}

View File

@@ -18,9 +18,11 @@ import Foundation
@MainActor
protocol RoomTimelineControllerFactoryProtocol {
// swiftlint:disable:next function_parameter_count
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol,
mediaPlayerProvider: MediaPlayerProviderProtocol,
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol) -> RoomTimelineControllerProtocol
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
secureBackupController: SecureBackupControllerProtocol) -> RoomTimelineControllerProtocol
}

View File

@@ -18,4 +18,5 @@ import Foundation
struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let isSessionVerified: Bool
}

View File

@@ -169,7 +169,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
}
return EncryptedRoomTimelineItem(id: eventItemProxy.id,
body: L10n.commonUnableToDecrypt,
body: L10n.commonWaitingForDecryptionKey,
encryptionType: encryptionType,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing,

View File

@@ -0,0 +1,21 @@
//
// 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 ElementX
import XCTest
@MainActor
class SecureBackupLogoutConfirmationScreenUITests: XCTestCase { }

View File

@@ -0,0 +1,22 @@
//
// 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 XCTest
@testable import ElementX
@MainActor
class SecureBackupLogoutConfirmationScreenViewModelTests: XCTestCase { }