diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index cee663bb2..ff999f9a0 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -415,6 +415,7 @@ 988BA75A182738150894A23F /* UserIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AE4B3273BA189FDCD4055C /* UserIndicator.swift */; }; 992477AB8E3F3C36D627D32E /* OnboardingViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BC4437C107D52ED19357DFC /* OnboardingViewModelProtocol.swift */; }; 992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; }; + 9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */; }; 99ED42B8F8D6BFB1DBCF4C45 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = D661CAB418C075A94306A792 /* AnalyticsEvents */; }; 99F8DA4CCC6772EE5FE68E24 /* ViewModelContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818CBE6249ED6E8FC30E8366 /* ViewModelContext.swift */; }; 9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; @@ -440,7 +441,6 @@ 9EBDC79CAC9B63A0D626E333 /* LegalInformationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */; }; 9F19096BFA629C0AC282B1E4 /* CreateRoomScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEB4634C0DD7779C4AB504 /* CreateRoomScreenUITests.swift */; }; 9FAF6DA7E8E85C9699757764 /* CollapsibleRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2656184491C505700D2405 /* CollapsibleRoomTimelineView.swift */; }; - A002DDF12A557DFF00228A9F /* AnalyticsLocationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A002DDF02A557DFF00228A9F /* AnalyticsLocationType.swift */; }; A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */; }; A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */; }; A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */; }; @@ -493,6 +493,7 @@ AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */; }; B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; }; B064D42BA087649ACAE462E8 /* SoftLogoutUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */; }; + B09DC6E3D0EE87C4D4ABFAB3 /* EncryptedHistoryRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */; }; B0CB16349B96262AA65A04AF /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = E9BAB8A793FE3B54CDD47102 /* URLRouting */; }; B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; }; B14BC354E56616B6B7D9A3D7 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */; }; @@ -665,6 +666,7 @@ EBE13FAB4E29738AC41BD3E5 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; EC280623A42904341363EAAF /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = A20EA00CCB9DBE0FFB17DD09 /* Collections */; }; ECA636DAF071C611FDC2BB57 /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; + EDC1031A7CFB3406A9DA3175 /* AnalyticsLocationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD6299F4516797E9BBE14C3 /* AnalyticsLocationType.swift */; }; EDF8919F15DE0FF00EF99E70 /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */; }; EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40F48279322E504153AB0D /* MockClientProxy.swift */; }; EE6933C935080B4E0348A58B /* EmojiMartCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C3AACCAA82392D08924496 /* EmojiMartCategory.swift */; }; @@ -826,7 +828,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; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; 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 = ""; }; @@ -961,7 +963,7 @@ 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = ""; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = ""; }; - 478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DesignKit; sourceTree = SOURCE_ROOT; }; + 478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DesignKit; path = DesignKit; sourceTree = SOURCE_ROOT; }; 4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = ""; }; 47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = ""; }; @@ -970,6 +972,7 @@ 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = ""; }; 4A542BC40D6EC2E66BC5659B /* TextBasedRoomTimelineViewMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewMock.swift; sourceTree = ""; }; 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = ""; }; + 4AD6299F4516797E9BBE14C3 /* AnalyticsLocationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsLocationType.swift; sourceTree = ""; }; 4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = ""; }; 4B5046BB295AEAFA6FB81655 /* SessionVerificationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenModels.swift; sourceTree = ""; }; 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = ""; }; @@ -1082,6 +1085,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 = ""; }; + 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineView.swift; sourceTree = ""; }; 75910F5A36EA8FF9BAD08D18 /* MigrationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenUITests.swift; sourceTree = ""; }; 772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = ""; }; 78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1128,7 +1132,7 @@ 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.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; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; 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 = ""; }; @@ -1172,7 +1176,6 @@ 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = ""; }; 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = ""; }; 9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = ""; }; - A002DDF02A557DFF00228A9F /* AnalyticsLocationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsLocationType.swift; sourceTree = ""; }; A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = ""; }; A05707BF550D770168A406DB /* LoginViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelTests.swift; sourceTree = ""; }; A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerProtocol.swift; sourceTree = ""; }; @@ -1235,7 +1238,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; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = ""; }; B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; @@ -1312,8 +1315,9 @@ CD6B0C4639E066915B5E6463 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; 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; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; 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 = ""; }; D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = ""; }; @@ -1382,7 +1386,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; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; 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 = ""; }; @@ -2071,7 +2075,7 @@ E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */, D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */, 57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */, - A002DDF02A557DFF00228A9F /* AnalyticsLocationType.swift */, + 4AD6299F4516797E9BBE14C3 /* AnalyticsLocationType.swift */, FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */, 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */, A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */, @@ -3028,6 +3032,7 @@ FC2D505742FDA21FCDC4C18A /* AudioRoomTimelineView.swift */, 6E2656184491C505700D2405 /* CollapsibleRoomTimelineView.swift */, 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */, + 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */, 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */, E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */, F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */, @@ -3223,6 +3228,7 @@ D977D4E565C06D3F41C8F8FC /* Virtual */ = { isa = PBXGroup; children = ( + D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */, 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */, DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */, C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */, @@ -4029,6 +4035,7 @@ F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */, 54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */, 8DDC6F28C797D8685F2F8E32 /* AnalyticsConsentState.swift in Sources */, + EDC1031A7CFB3406A9DA3175 /* AnalyticsLocationType.swift in Sources */, 9DF3F6318A4402305F5EB869 /* AnalyticsPromptScreen.swift in Sources */, 10516CF20E8B5852F4C444FD /* AnalyticsPromptScreenCheckmarkItem.swift in Sources */, 5F28C9146694B381BB82E18C /* AnalyticsPromptScreenCoordinator.swift in Sources */, @@ -4133,6 +4140,8 @@ 8B41D0357B91CD3B6F6A3BCA /* EmoteRoomTimelineItemContent.swift in Sources */, 68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */, 8B1D5CE017EEC734CF5FE130 /* Encodable.swift in Sources */, + B09DC6E3D0EE87C4D4ABFAB3 /* EncryptedHistoryRoomTimelineItem.swift in Sources */, + 9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */, 4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */, B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */, 69ABFBAF05D7EF11E7C88CEA /* EncryptionSyncListenerProxy.swift in Sources */, @@ -4300,7 +4309,6 @@ 46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */, 42A5A42ACF063EEE6B1980D2 /* ReportContentScreenViewModel.swift in Sources */, 8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */, - A002DDF12A557DFF00228A9F /* AnalyticsLocationType.swift in Sources */, A494741843F087881299ACF0 /* RestorationToken.swift in Sources */, 755EE5B0998C6A4D764D86E5 /* RoomAttachmentPicker.swift in Sources */, 0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */, diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index ff0787277..5d0b95441 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -283,6 +283,7 @@ "screen_room_details_room_name_label" = "Room name"; "screen_room_details_share_room_title" = "Share room"; "screen_room_details_updating_room" = "Updating room…"; +"screen_room_encrypted_history_banner" = "Message history is currently unavailable in this room"; "screen_room_error_failed_retrieving_user_details" = "Could not retrieve user details"; "screen_room_invite_again_alert_message" = "Would you like to invite them back?"; "screen_room_invite_again_alert_title" = "You are alone in this chat"; @@ -335,8 +336,8 @@ "screen_waitlist_message_success" = "Welcome to %1$@!"; "screen_waitlist_title" = "You’re almost there."; "screen_waitlist_title_success" = "You're in."; -"session_verification_banner_message" = "Looks like you’re using a new device. Verify it’s you to access your encrypted messages."; -"session_verification_banner_title" = "Access your message history"; +"session_verification_banner_message" = "Looks like you’re using a new device. Verify with another device to access your encrypted messages moving forwards."; +"session_verification_banner_title" = "Verify it’s you"; "settings_rageshake" = "Rageshake"; "settings_rageshake_detection_threshold" = "Detection threshold"; "settings_title_general" = "General"; diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 1e06304b0..6baf3e9b2 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -22,6 +22,7 @@ final class AppSettings { private enum UserDefaultsKeys: String { case lastVersionLaunched case seenInvites + case lastLoginDate case migratedAccounts case timelineStyle case analyticsConsentState @@ -105,6 +106,13 @@ final class AppSettings { /// Any pre-defined static client registrations for OIDC issuers. let oidcStaticRegistrations: [URL: String] = ["https://id.thirdroom.io/realms/thirdroom": "elementx"] + /// The date that the call to `/login` completed successfully. This is used to put + /// a hard wall on the history of encrypted messages until we have key backup. + /// + /// Not a multi-account aware setting as key backup will come before multi-account. + @UserPreference(key: UserDefaultsKeys.lastLoginDate, defaultValue: nil, storageType: .userDefaults(store)) + var lastLoginDate: Date? + /// A dictionary of accounts that have performed an initial sync through their proxy. /// /// This is a temporary workaround. In the future we should be able to receive a signal from the diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 50b401bcf..b1e40e88c 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -271,15 +271,14 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { self.roomProxy = roomProxy - let userId = userSession.clientProxy.userID + let userID = userSession.clientProxy.userID - let timelineItemFactory = RoomTimelineItemFactory(userID: userId, + let timelineItemFactory = RoomTimelineItemFactory(userID: userID, mediaProvider: userSession.mediaProvider, attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: appSettings.permalinkBaseURL), - stateEventStringBuilder: RoomStateEventStringBuilder(userID: userId)) + stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)) - let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(userId: userId, - roomProxy: roomProxy, + let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: userSession.mediaProvider) self.timelineController = timelineController diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 821294cfb..386ed83ae 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -718,6 +718,8 @@ public enum L10n { public static var screenRoomDetailsTopicTitle: String { return L10n.tr("Localizable", "screen_room_details_topic_title") } /// Updating room… public static var screenRoomDetailsUpdatingRoom: String { return L10n.tr("Localizable", "screen_room_details_updating_room") } + /// Message history is currently unavailable in this room + public static var screenRoomEncryptedHistoryBanner: String { return L10n.tr("Localizable", "screen_room_encrypted_history_banner") } /// Failed processing media to upload, please try again. public static var screenRoomErrorFailedProcessingMedia: String { return L10n.tr("Localizable", "screen_room_error_failed_processing_media") } /// Could not retrieve user details @@ -848,9 +850,9 @@ public enum L10n { public static var screenWaitlistTitle: String { return L10n.tr("Localizable", "screen_waitlist_title") } /// You're in. public static var screenWaitlistTitleSuccess: String { return L10n.tr("Localizable", "screen_waitlist_title_success") } - /// Looks like you’re using a new device. Verify it’s you to access your encrypted messages. + /// Looks like you’re using a new device. Verify with another device to access your encrypted messages moving forwards. public static var sessionVerificationBannerMessage: String { return L10n.tr("Localizable", "session_verification_banner_message") } - /// Access your message history + /// Verify it’s you public static var sessionVerificationBannerTitle: String { return L10n.tr("Localizable", "session_verification_banner_title") } /// Rageshake public static var settingsRageshake: String { return L10n.tr("Localizable", "settings_rageshake") } diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift index 8711ec486..de9bd2a0f 100644 --- a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift @@ -205,6 +205,8 @@ class AuthenticationCoordinator: CoordinatorProtocol { } private func userHasSignedIn(userSession: UserSessionProtocol) { + appSettings.lastLoginDate = .now + showAnalyticsPromptIfNeeded { [weak self] in guard let self else { return } self.delegate?.authenticationCoordinator(self, didLoginWithSession: userSession) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift new file mode 100644 index 000000000..a395ce401 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedHistoryRoomTimelineView.swift @@ -0,0 +1,61 @@ +// +// 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 SwiftUI + +/// The item shown when all previous items are un-decryptable due to +/// key backup not yet being supported in the app. +struct EncryptedHistoryRoomTimelineView: View { + let timelineItem: EncryptedHistoryRoomTimelineItem + + var body: some View { + Label { + Text(L10n.screenRoomEncryptedHistoryBanner) + .font(.compound.bodyMDSemibold) + .foregroundColor(.compound.textInfoPrimary) + } icon: { + Image(systemName: "info.circle.fill") + .foregroundColor(.compound.iconInfoPrimary) + } + .labelStyle(EncryptedHistoryLabelStyle()) + .padding(16) + .background { + RoundedRectangle(cornerRadius: 8) + .fill(Color.compound.bgInfoSubtle) + RoundedRectangle(cornerRadius: 8) + .stroke(Color.compound.borderInfoSubtle) + } + .frame(maxWidth: .infinity) + .padding(.horizontal, 8) + .padding(.vertical, 16) + } +} + +private struct EncryptedHistoryLabelStyle: LabelStyle { + func makeBody(configuration: Configuration) -> some View { + HStack(alignment: .firstTextBaseline, spacing: 16) { + configuration.icon + configuration.title + } + } +} + +struct EncryptedHistoryRoomTimelineView_Previews: PreviewProvider { + static var previews: some View { + let item = EncryptedHistoryRoomTimelineItem(id: UUID().uuidString) + EncryptedHistoryRoomTimelineView(timelineItem: item) + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineTableViewController.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineTableViewController.swift index 3ee4d2d79..b838cd020 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineTableViewController.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineTableViewController.swift @@ -119,6 +119,13 @@ class TimelineTableViewController: UIViewController { // for each possible cell, causing layout issues tableView.accessibilityElementsHidden = Tests.shouldDisableTimelineAccessibility + tableView.publisher(for: \.contentSize) + .removeDuplicates() + .sink { [weak self] _ in + self?.updateTopPadding() + } + .store(in: &cancellables) + scrollToBottomPublisher .sink { [weak self] _ in self?.scrollToBottom(animated: true) @@ -242,9 +249,11 @@ class TimelineTableViewController: UIViewController { snapshot.appendSections([.main]) snapshot.appendItems(timelineItemsIDs) dataSource.apply(snapshot, animatingDifferences: false) - + + // Probably redundant now we observe content size changes… + // Leaving in place for the release and will reassess after. updateTopPadding() - + if previousLayout.isBottomVisible { scrollToBottom(animated: false) } else if let pinnedItem = previousLayout.pinnedItem { @@ -283,7 +292,8 @@ class TimelineTableViewController: UIViewController { let contentHeight = tableView.contentSize.height - headerHeight let newHeight = max(0, tableView.visibleSize.height - contentHeight) - guard newHeight != headerHeight else { return } + // Round the check to account floating point accuracy during keyboard appearance. + guard newHeight.rounded() != headerHeight.rounded() else { return } if newHeight > 0 { let frame = CGRect(origin: .zero, size: CGSize(width: tableView.contentSize.width, height: newHeight)) diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift index a69e538b6..b4b89cd84 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineControllerFactory.swift @@ -17,8 +17,7 @@ import Foundation struct MockRoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol { - func buildRoomTimelineController(userId: String, - roomProxy: RoomProxyProtocol, + func buildRoomTimelineController(roomProxy: RoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol { let timelineController = MockRoomTimelineController() diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index 2200716a2..4bfa58a23 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -19,7 +19,6 @@ import Foundation import UIKit class RoomTimelineController: RoomTimelineControllerProtocol { - private let userId: String private let roomProxy: RoomProxyProtocol private let timelineProvider: RoomTimelineProviderProtocol private let timelineItemFactory: RoomTimelineItemFactoryProtocol @@ -42,12 +41,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol { roomProxy.id } - init(userId: String, - roomProxy: RoomProxyProtocol, + init(roomProxy: RoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol, appSettings: AppSettings) { - self.userId = userId self.roomProxy = roomProxy timelineProvider = roomProxy.timelineProvider self.timelineItemFactory = timelineItemFactory @@ -246,6 +243,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { var newTimelineItems = [RoomTimelineItemProtocol]() var canBackPaginate = true var isBackPaginating = false + var lastEncryptedHistoryItemIndex: Int? let collapsibleChunks = timelineProvider.itemsPublisher.value.groupBy { isItemCollapsible($0) } @@ -264,6 +262,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol { isBackPaginating = true } else if timelineItem is TimelineStartRoomTimelineItem { canBackPaginate = false + } else if timelineItem is EncryptedHistoryRoomTimelineItem { + canBackPaginate = false } return timelineItem @@ -280,11 +280,21 @@ class RoomTimelineController: RoomTimelineControllerProtocol { continue } + if timelineItem is EncryptedHistoryRoomTimelineItem { + lastEncryptedHistoryItemIndex = newTimelineItems.endIndex + } + newTimelineItems.append(timelineItem) } else { newTimelineItems.append(CollapsibleTimelineItem(items: items)) } } + + if let lastEncryptedHistoryItemIndex { + // Remove everything up to the last encrypted history item. + // It only contains encrypted messages, state changes and date separators. + newTimelineItems.removeFirst(lastEncryptedHistoryItemIndex) + } timelineItems = newTimelineItems @@ -298,6 +308,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol { case .event(let eventTimelineItem): let timelineItem = timelineItemFactory.buildTimelineItem(for: eventTimelineItem) + if timelineItem is EncryptedRoomTimelineItem, isItemInEncryptionHistory(eventTimelineItem) { + return EncryptedHistoryRoomTimelineItem(id: eventTimelineItem.id) + } + if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol { // Avoid fetching this over and over again as it changes states if it keeps failing to load // Errors will be handled again on appearance @@ -326,6 +340,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } } + /// Whether or not a specific item is part of the room's history that can't be decrypted due + /// to the lack of key-backup. This is handled differently so we only show a single item. + private func isItemInEncryptionHistory(_ itemProxy: EventTimelineItemProxy) -> Bool { + guard roomProxy.isEncrypted, let lastLoginDate = appSettings.lastLoginDate else { return false } + return itemProxy.timestamp < lastLoginDate + } + private func isItemCollapsible(_ item: TimelineItemProxy) -> Bool { if !appSettings.shouldCollapseRoomStateEvents { return false diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift index dbd5dcac2..443666dd1 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactory.swift @@ -17,12 +17,10 @@ import Foundation struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol { - func buildRoomTimelineController(userId: String, - roomProxy: RoomProxyProtocol, + func buildRoomTimelineController(roomProxy: RoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol { - RoomTimelineController(userId: userId, - roomProxy: roomProxy, + RoomTimelineController(roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider, appSettings: ServiceLocator.shared.settings) diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift index 28ffa43e4..471794545 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerFactoryProtocol.swift @@ -18,8 +18,7 @@ import Foundation @MainActor protocol RoomTimelineControllerFactoryProtocol { - func buildRoomTimelineController(userId: String, - roomProxy: RoomProxyProtocol, + func buildRoomTimelineController(roomProxy: RoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift new file mode 100644 index 000000000..48030d57c --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Virtual/EncryptedHistoryRoomTimelineItem.swift @@ -0,0 +1,21 @@ +// +// 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 Foundation + +struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable { + let id: String +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift index 3c3fa232d..717c8e8b1 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift @@ -55,6 +55,8 @@ struct RoomTimelineItemView: View { UnsupportedRoomTimelineView(timelineItem: item) case .timelineStart(let item): TimelineStartRoomTimelineView(timelineItem: item) + case .encryptedHistory(let item): + EncryptedHistoryRoomTimelineView(timelineItem: item) case .state(let item): StateRoomTimelineView(timelineItem: item) case .group(let item): diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewModel.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewModel.swift index 72cc4422b..7c65bf0fe 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewModel.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewModel.swift @@ -58,6 +58,7 @@ enum RoomTimelineItemType: Equatable { case sticker(StickerRoomTimelineItem) case unsupported(UnsupportedRoomTimelineItem) case timelineStart(TimelineStartRoomTimelineItem) + case encryptedHistory(EncryptedHistoryRoomTimelineItem) case state(StateRoomTimelineItem) case group(CollapsibleTimelineItem) case location(LocationRoomTimelineItem) @@ -95,6 +96,8 @@ enum RoomTimelineItemType: Equatable { self = .unsupported(item) case let item as TimelineStartRoomTimelineItem: self = .timelineStart(item) + case let item as EncryptedHistoryRoomTimelineItem: + self = .encryptedHistory(item) case let item as StateRoomTimelineItem: self = .state(item) case let item as CollapsibleTimelineItem: @@ -123,6 +126,7 @@ enum RoomTimelineItemType: Equatable { .sticker(let item as RoomTimelineItemProtocol), .unsupported(let item as RoomTimelineItemProtocol), .timelineStart(let item as RoomTimelineItemProtocol), + .encryptedHistory(let item as RoomTimelineItemProtocol), .state(let item as RoomTimelineItemProtocol), .group(let item as RoomTimelineItemProtocol), .location(let item as RoomTimelineItemProtocol): @@ -137,7 +141,7 @@ enum RoomTimelineItemType: Equatable { return true case .redacted, .encrypted, .unsupported, .state: // Event based items that aren't reactable return false - case .timelineStart, .separator, .readMarker, .paginationIndicator: // Virtual items are never reactable + case .timelineStart, .encryptedHistory, .separator, .readMarker, .paginationIndicator: // Virtual items are never reactable return false case .group: return false diff --git a/changelog.d/1251.change b/changelog.d/1251.change new file mode 100644 index 000000000..ec83d9945 --- /dev/null +++ b/changelog.d/1251.change @@ -0,0 +1 @@ +Add encryption history banner. \ No newline at end of file