diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 2e7f3ec27..7c3852c0f 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -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 = ""; }; 12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = ""; }; 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; @@ -1095,6 +1103,7 @@ 1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = ""; }; 1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfigurationTests.swift; sourceTree = ""; }; 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = ""; }; + 184D68B72AE7A01400141160 /* SettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFlowCoordinator.swift; sourceTree = ""; }; 1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReadReceiptsView.swift; sourceTree = ""; }; 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxy.swift; sourceTree = ""; }; 18FE0CDF1FFA92EA7EE17B0B /* RoomTimelineControllerFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactoryProtocol.swift; sourceTree = ""; }; @@ -1115,6 +1124,7 @@ 1B8E176484A89BAC389D4076 /* RoomMembersListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreen.swift; sourceTree = ""; }; 1B927CF5EF7FCCDA5EDC474B /* NotificationItemProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxyProtocol.swift; sourceTree = ""; }; 1C21A715237F2B6D6E80998C /* SecureBackupControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerProtocol.swift; sourceTree = ""; }; + 1CC09F30B0E1010951952BDC /* SecureBackupLogoutConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenUITests.swift; sourceTree = ""; }; 1CC575D1895FA62591451A93 /* RoomMemberDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreen.swift; sourceTree = ""; }; 1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyProtocol.swift; sourceTree = ""; }; 1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; @@ -1205,6 +1215,7 @@ 37A63A59BFDDC494B1C20119 /* CallScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenViewModel.swift; sourceTree = ""; }; 37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringTests.swift; sourceTree = ""; }; 37E727F7E0BCE8A0BBFD33FF /* OnboardingScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreenCoordinator.swift; sourceTree = ""; }; + 37FEE10AB666891E6A675E5E /* SecureBackupLogoutConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreen.swift; sourceTree = ""; }; 382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyMock.swift; sourceTree = ""; }; 38345442415E07A931197C55 /* AppLockScreenPINKeypad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenPINKeypad.swift; sourceTree = ""; }; 38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreen.swift; sourceTree = ""; }; @@ -1413,6 +1424,7 @@ 7475C5AE20BA896930907EA8 /* AudioRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRoomTimelineItemContent.swift; sourceTree = ""; }; 748AE77AC3B0A01223033B87 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenCoordinator.swift; sourceTree = ""; }; + 74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelProtocol.swift; sourceTree = ""; }; 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signposter.swift; sourceTree = ""; }; 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineView.swift; sourceTree = ""; }; 75910F5A36EA8FF9BAD08D18 /* MigrationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenUITests.swift; sourceTree = ""; }; @@ -1435,6 +1447,7 @@ 7CA3F8E905DF50BF22ECC18F /* ThreadDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDecorator.swift; sourceTree = ""; }; 7D0CBC76C80E04345E11F2DB /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactoryProtocol.swift; sourceTree = ""; }; + 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModel.swift; sourceTree = ""; }; 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 = ""; }; @@ -1455,6 +1468,7 @@ 840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenModels.swift; sourceTree = ""; }; 845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersViewModelTests.swift; sourceTree = ""; }; 84816E0D2F34E368BF64FA60 /* SessionVerificationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreen.swift; sourceTree = ""; }; + 848F69921527D31CAACB93AF /* SecureBackupLogoutConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenViewModelTests.swift; sourceTree = ""; }; 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreen.swift; sourceTree = ""; }; 84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModel.swift; sourceTree = ""; }; 84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunnerTests.swift; sourceTree = ""; }; @@ -1489,7 +1503,7 @@ 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = ""; }; 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 = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = ""; }; 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; @@ -1624,7 +1638,7 @@ B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = ""; }; B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1727,7 +1741,7 @@ CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = ""; }; CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; @@ -1777,6 +1791,7 @@ DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = ""; }; DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = ""; }; DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = ""; }; + DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenModels.swift; sourceTree = ""; }; DD97F9661ABF08CE002054A2 /* AppLockServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceTests.swift; sourceTree = ""; }; DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenUITests.swift; sourceTree = ""; }; DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = ""; }; @@ -1784,6 +1799,7 @@ DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = ""; }; E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerAuthorization.swift; sourceTree = ""; }; E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; 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 = ""; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarSize.swift; sourceTree = ""; }; @@ -1827,7 +1843,7 @@ ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; @@ -1842,7 +1858,7 @@ F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; - F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; + F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = ""; }; @@ -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 = ""; @@ -2954,6 +2972,18 @@ path = View; sourceTree = ""; }; + 63E514D74481A3D9556DFFC3 /* SecureBackupLogoutConfirmationScreen */ = { + isa = PBXGroup; + children = ( + E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */, + DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */, + 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */, + 74E08B8A66948E9690F38B94 /* SecureBackupLogoutConfirmationScreenViewModelProtocol.swift */, + FB4987F1C660F2085258866B /* View */, + ); + path = SecureBackupLogoutConfirmationScreen; + sourceTree = ""; + }; 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 = ""; }; + FB4987F1C660F2085258866B /* View */ = { + isa = PBXGroup; + children = ( + 37FEE10AB666891E6A675E5E /* SecureBackupLogoutConfirmationScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 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 */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index ad9f55512..4323cb9e9 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -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) diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 8da744e26..482399406 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -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 diff --git a/ElementX/Sources/Application/Navigation/AppRoutes.swift b/ElementX/Sources/Application/Navigation/AppRoutes.swift index 5ab60e053..8b1573fca 100644 --- a/ElementX/Sources/Application/Navigation/AppRoutes.swift +++ b/ElementX/Sources/Application/Navigation/AppRoutes.swift @@ -24,6 +24,8 @@ enum AppRoute: Equatable { case roomMemberDetails(userID: String) case invites case genericCallLink(url: URL) + case settings + case chatBackupSettings } struct AppRouteURLParser { diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index ff4a5d35e..330e944ac 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -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) diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift new file mode 100644 index 000000000..abf7de30e --- /dev/null +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -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() + + private let actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { + 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) + } +} diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index aaf4dbb18..e7af9b0a3 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -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() 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) + } } diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift index 79a870cf6..02b088515 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift @@ -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 @@ -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 } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index d182898e0..1cedf6dd6 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -984,6 +984,24 @@ class MediaPlayerProviderMock: MediaPlayerProviderProtocol { await detachAllStatesExceptClosure?(exception) } } +class NetworkMonitorMock: NetworkMonitorProtocol { + var reachabilityPublisher: CurrentValuePublisher { + get { return underlyingReachabilityPublisher } + set(value) { underlyingReachabilityPublisher = value } + } + var underlyingReachabilityPublisher: CurrentValuePublisher! + 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! + 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 { diff --git a/ElementX/Sources/Other/NetworkMonitor/NetworkMonitorProtocol.swift b/ElementX/Sources/Other/NetworkMonitor/NetworkMonitorProtocol.swift index 29223e558..33436f4a6 100644 --- a/ElementX/Sources/Other/NetworkMonitor/NetworkMonitorProtocol.swift +++ b/ElementX/Sources/Other/NetworkMonitor/NetworkMonitorProtocol.swift @@ -26,3 +26,6 @@ protocol NetworkMonitorProtocol { var isCurrentConnectionExpensive: Bool { get } var isCurrentConnectionConstrained: Bool { get } } + +// sourcery: AutoMockable +extension NetworkMonitorProtocol { } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index 5da492345..c7bbd9e1d 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -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: diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index b0015407c..9990d3dbe 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -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 { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index da2fcc5c1..688dec9de 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -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) diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenUserMenuButton.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenUserMenuButton.swift index 9b9462100..5a228bbe4 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenUserMenuButton.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenUserMenuButton.swift @@ -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) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift index 6365bf226..82c64e541 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift @@ -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)) + } } } diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/View/SecureBackupKeyBackupScreen.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/View/SecureBackupKeyBackupScreen.swift index cf0544518..2b797924a 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/View/SecureBackupKeyBackupScreen.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupKeyBackupScreen/View/SecureBackupKeyBackupScreen.swift @@ -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() diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenCoordinator.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenCoordinator.swift new file mode 100644 index 000000000..ab30df1e4 --- /dev/null +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenCoordinator.swift @@ -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 = .init() + private var cancellables = Set() + + var actions: AnyPublisher { + 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)) + } +} diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenModels.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenModels.swift new file mode 100644 index 000000000..07949ad2a --- /dev/null +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenModels.swift @@ -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 +} diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModel.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModel.swift new file mode 100644 index 000000000..e561e425b --- /dev/null +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModel.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 + +typealias SecureBackupLogoutConfirmationScreenViewModelType = StateStoreViewModel + +class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmationScreenViewModelType, SecureBackupLogoutConfirmationScreenViewModelProtocol { + private let secureBackupController: SecureBackupControllerProtocol + private let networkMonitor: NetworkMonitorProtocol + + @CancellableTask + private var keyUploadWaitingTask: Task? + + private var actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { + 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) + } + } +} diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModelProtocol.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModelProtocol.swift new file mode 100644 index 000000000..473626eea --- /dev/null +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/SecureBackupLogoutConfirmationScreenViewModelProtocol.swift @@ -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 { get } + var context: SecureBackupLogoutConfirmationScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift new file mode 100644 index 000000000..0fb4ba9a5 --- /dev/null +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupLogoutConfirmationScreen/View/SecureBackupLogoutConfirmationScreen.swift @@ -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(.enabled).asCurrentValuePublisher() + + let networkMonitor = NetworkMonitorMock() + networkMonitor.underlyingReachabilityPublisher = CurrentValueSubject(.reachable).asCurrentValuePublisher() + + return SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: secureBackupController, + networkMonitor: networkMonitor) + } +} diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift index 0d7ecd709..295accb13 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupRecoveryKeyScreen/View/SecureBackupRecoveryKeyScreen.swift @@ -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() diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift index 57cb71de4..aadb9f309 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift @@ -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, diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index e853a512f..a248d4427 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -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 diff --git a/ElementX/Sources/Services/SecureBackup/SecureBackupController.swift b/ElementX/Sources/Services/SecureBackup/SecureBackupController.swift index bf2abc544..be0b49b9a 100644 --- a/ElementX/Sources/Services/SecureBackup/SecureBackupController.swift +++ b/ElementX/Sources/Services/SecureBackup/SecureBackupController.swift @@ -29,6 +29,11 @@ class SecureBackupController: SecureBackupControllerProtocol { keyBackupStateSubject.asCurrentValuePublisher() } + var isLastSession: Bool { + #warning("FIXME") + return true + } + func enableBackup() async -> Result { keyBackupStateSubject.send(.enabling) @@ -76,4 +81,8 @@ class SecureBackupController: SecureBackupControllerProtocol { return .success(()) } + + func waitForKeyBackup() async { + try? await Task.sleep(for: .seconds(5)) + } } diff --git a/ElementX/Sources/Services/SecureBackup/SecureBackupControllerProtocol.swift b/ElementX/Sources/Services/SecureBackup/SecureBackupControllerProtocol.swift index 46197d17c..ba04c326a 100644 --- a/ElementX/Sources/Services/SecureBackup/SecureBackupControllerProtocol.swift +++ b/ElementX/Sources/Services/SecureBackup/SecureBackupControllerProtocol.swift @@ -49,11 +49,15 @@ protocol SecureBackupControllerProtocol { var keyBackupState: CurrentValuePublisher { get } + var isLastSession: Bool { get } + func enableBackup() async -> Result func disableBackup() async -> Result func generateRecoveryKey() async -> Result func confirmRecoveryKey(_ key: String) async -> Result + + func waitForKeyBackup() async } extension SecureBackupControllerMock { diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift index 2bee5ea26..968fde5b0 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift @@ -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 diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index 8f030da4a..1b3ef5689 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -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() @@ -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 { diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift index c097370f3..0d36b5088 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift @@ -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) } } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift index 17e7d0aa5..cb05f7f76 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift @@ -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 } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift index 22da0afab..13d293538 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift @@ -18,4 +18,5 @@ import Foundation struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Equatable { let id: TimelineItemIdentifier + let isSessionVerified: Bool } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index c245f0bbd..402f7bc20 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -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, diff --git a/UITests/Sources/SecureBackupLogoutConfirmationScreenUITests.swift b/UITests/Sources/SecureBackupLogoutConfirmationScreenUITests.swift new file mode 100644 index 000000000..822755ccf --- /dev/null +++ b/UITests/Sources/SecureBackupLogoutConfirmationScreenUITests.swift @@ -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 { } diff --git a/UnitTests/Sources/SecureBackupLogoutConfirmationScreenViewModelTests.swift b/UnitTests/Sources/SecureBackupLogoutConfirmationScreenViewModelTests.swift new file mode 100644 index 000000000..7b0ec3197 --- /dev/null +++ b/UnitTests/Sources/SecureBackupLogoutConfirmationScreenViewModelTests.swift @@ -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 { }