mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Add mechanism for opening up a threaded timeline
This commit is contained in:
committed by
Stefan Ceriu
parent
2f938d3cbf
commit
10fb2b5dda
@@ -69,6 +69,7 @@
|
|||||||
08CB4BD12CEEDE6AAE4A18DD /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035177BCD8E8308B098AC3C2 /* WindowManager.swift */; };
|
08CB4BD12CEEDE6AAE4A18DD /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035177BCD8E8308B098AC3C2 /* WindowManager.swift */; };
|
||||||
095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; };
|
095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; };
|
||||||
095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */; };
|
095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */; };
|
||||||
|
09693596AA3CA4E598C0D2F1 /* ThreadTimelineScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73FEE625AB52042049DB9268 /* ThreadTimelineScreenCoordinator.swift */; };
|
||||||
09713669577CDA8D012EE380 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6647C55D93508C7CE9D954A5 /* MatrixRustSDK */; };
|
09713669577CDA8D012EE380 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6647C55D93508C7CE9D954A5 /* MatrixRustSDK */; };
|
||||||
09D3D7D115318CAD131B4FE7 /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57084488B03BDB33C7B7CA0E /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift */; };
|
09D3D7D115318CAD131B4FE7 /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57084488B03BDB33C7B7CA0E /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift */; };
|
||||||
0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; };
|
0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; };
|
||||||
@@ -483,6 +484,7 @@
|
|||||||
5DB4334CBBA142376FF5FFEC /* preview_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 200626E8353AB2729444F991 /* preview_image.jpg */; };
|
5DB4334CBBA142376FF5FFEC /* preview_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 200626E8353AB2729444F991 /* preview_image.jpg */; };
|
||||||
5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */; };
|
5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */; };
|
||||||
5DFC2A889D3B39DD47AC63A8 /* PillUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C537DE821FED94D23467B6C4 /* PillUtilities.swift */; };
|
5DFC2A889D3B39DD47AC63A8 /* PillUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C537DE821FED94D23467B6C4 /* PillUtilities.swift */; };
|
||||||
|
5EB116B58533C9A0EBA22717 /* ThreadTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC2E7CEE6E9314FD69FE0ED9 /* ThreadTimelineScreen.swift */; };
|
||||||
5EC046E41755C095DAB1C3FF /* TimelineProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8C9BBB729C941BEE0E2A63 /* TimelineProviderProtocol.swift */; };
|
5EC046E41755C095DAB1C3FF /* TimelineProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8C9BBB729C941BEE0E2A63 /* TimelineProviderProtocol.swift */; };
|
||||||
5EDBDE802761B5ECB54E6787 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2711E5996016ABD6EAAEB58A /* LogLevel.swift */; };
|
5EDBDE802761B5ECB54E6787 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2711E5996016ABD6EAAEB58A /* LogLevel.swift */; };
|
||||||
5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; };
|
5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; };
|
||||||
@@ -586,6 +588,7 @@
|
|||||||
71C1347F23868324A4F43940 /* NavigationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A22A05E472533ED3C5A31B3 /* NavigationModule.swift */; };
|
71C1347F23868324A4F43940 /* NavigationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A22A05E472533ED3C5A31B3 /* NavigationModule.swift */; };
|
||||||
7254FB2EFDD43BC8BB7A1213 /* SecurityAndPrivacyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AE42C19EDE64B7CB7BE4D0 /* SecurityAndPrivacyScreen.swift */; };
|
7254FB2EFDD43BC8BB7A1213 /* SecurityAndPrivacyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AE42C19EDE64B7CB7BE4D0 /* SecurityAndPrivacyScreen.swift */; };
|
||||||
733E2B19AB1FDA3B93293A28 /* AppLockSetupPINScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F275432954C8C6B1B7D966 /* AppLockSetupPINScreen.swift */; };
|
733E2B19AB1FDA3B93293A28 /* AppLockSetupPINScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F275432954C8C6B1B7D966 /* AppLockSetupPINScreen.swift */; };
|
||||||
|
738288EAEE235CAC0893AB9E /* ThreadTimelineScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C9ACDD96F36510C1FC0836B /* ThreadTimelineScreenViewModel.swift */; };
|
||||||
73F33E9776B7A50B65A031D2 /* AppLockSettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0BA67B3E4EF9D29D14A78CE /* AppLockSettingsScreenViewModelTests.swift */; };
|
73F33E9776B7A50B65A031D2 /* AppLockSettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0BA67B3E4EF9D29D14A78CE /* AppLockSettingsScreenViewModelTests.swift */; };
|
||||||
73F547BEB41D3DAFAAF6E0AF /* UserProfileScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E2E5103702D13361D09100 /* UserProfileScreenViewModelTests.swift */; };
|
73F547BEB41D3DAFAAF6E0AF /* UserProfileScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E2E5103702D13361D09100 /* UserProfileScreenViewModelTests.swift */; };
|
||||||
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0CBC76C80E04345E11F2DB /* Application.swift */; };
|
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0CBC76C80E04345E11F2DB /* Application.swift */; };
|
||||||
@@ -703,6 +706,7 @@
|
|||||||
877D3CE8680536DB430DE6A2 /* TimelineItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48C91C8BE55CAE1A3DBC3BC /* TimelineItemIdentifier.swift */; };
|
877D3CE8680536DB430DE6A2 /* TimelineItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48C91C8BE55CAE1A3DBC3BC /* TimelineItemIdentifier.swift */; };
|
||||||
878070573C7BF19E735707B4 /* RoomTimelineItemProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */; };
|
878070573C7BF19E735707B4 /* RoomTimelineItemProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */; };
|
||||||
87CEA3E07B602705BC2D2A20 /* ClientBuilderHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0CD1CAFD3F8B057F9AEA5 /* ClientBuilderHook.swift */; };
|
87CEA3E07B602705BC2D2A20 /* ClientBuilderHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0CD1CAFD3F8B057F9AEA5 /* ClientBuilderHook.swift */; };
|
||||||
|
87E8C31FCF9276F15CB0B408 /* ThreadTimelineScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1C19A4BE46EDE1411ECCE /* ThreadTimelineScreenViewModelProtocol.swift */; };
|
||||||
8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */; };
|
8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */; };
|
||||||
88356DE7F2AD243AB10C7B7A /* Signposter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */; };
|
88356DE7F2AD243AB10C7B7A /* Signposter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */; };
|
||||||
887AC93C523AEFB640EA5EC8 /* TextBasedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E33FD32BBC44D703C7AE4F9 /* TextBasedRoomTimelineItem.swift */; };
|
887AC93C523AEFB640EA5EC8 /* TextBasedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E33FD32BBC44D703C7AE4F9 /* TextBasedRoomTimelineItem.swift */; };
|
||||||
@@ -850,6 +854,7 @@
|
|||||||
A3D7110C1E75E7B4A73BE71C /* VoiceMessageRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93C94C30E3135BC9290DE13 /* VoiceMessageRecorderTests.swift */; };
|
A3D7110C1E75E7B4A73BE71C /* VoiceMessageRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93C94C30E3135BC9290DE13 /* VoiceMessageRecorderTests.swift */; };
|
||||||
A439B456D0761D6541745CC3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; };
|
A439B456D0761D6541745CC3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; };
|
||||||
A440D4BC02088482EC633A88 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
|
A440D4BC02088482EC633A88 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
|
||||||
|
A46B93EA564F539CEC252ECC /* ThreadTimelineScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69AEA8755382DB34892FB7B /* ThreadTimelineScreenModels.swift */; };
|
||||||
A494741843F087881299ACF0 /* RestorationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558A15CFB934F9229301527 /* RestorationToken.swift */; };
|
A494741843F087881299ACF0 /* RestorationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558A15CFB934F9229301527 /* RestorationToken.swift */; };
|
||||||
A4AF12D9D8BA34B3B7B55B08 /* AuthenticationStartScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6663BFB9FDB8752562CD12CA /* AuthenticationStartScreenCoordinator.swift */; };
|
A4AF12D9D8BA34B3B7B55B08 /* AuthenticationStartScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6663BFB9FDB8752562CD12CA /* AuthenticationStartScreenCoordinator.swift */; };
|
||||||
A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FA515B3B0D61EF1E907D2D /* BadgeView.swift */; };
|
A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FA515B3B0D61EF1E907D2D /* BadgeView.swift */; };
|
||||||
@@ -1924,6 +1929,7 @@
|
|||||||
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportFlowCoordinator.swift; sourceTree = "<group>"; };
|
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||||
739077686814E4EA339B1C83 /* RoomPreviewProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPreviewProxyProtocol.swift; sourceTree = "<group>"; };
|
739077686814E4EA339B1C83 /* RoomPreviewProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPreviewProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
73A5C3F7C9C1DA10CAEC6A98 /* VoiceMessageRecordingComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingComposer.swift; sourceTree = "<group>"; };
|
73A5C3F7C9C1DA10CAEC6A98 /* VoiceMessageRecordingComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingComposer.swift; sourceTree = "<group>"; };
|
||||||
|
73FEE625AB52042049DB9268 /* ThreadTimelineScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTimelineScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
7447C0AD7EF302CD027D6230 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SAS.strings; sourceTree = "<group>"; };
|
7447C0AD7EF302CD027D6230 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SAS.strings; sourceTree = "<group>"; };
|
||||||
7463464054DDF194C54F0B04 /* LogViewerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
7463464054DDF194C54F0B04 /* LogViewerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
74653BE903970C0E36867D46 /* GlobalSearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenCoordinator.swift; sourceTree = "<group>"; };
|
74653BE903970C0E36867D46 /* GlobalSearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -1963,6 +1969,7 @@
|
|||||||
7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenViewModel.swift; sourceTree = "<group>"; };
|
7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
7C9ACDD96F36510C1FC0836B /* ThreadTimelineScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTimelineScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
7D0CBC76C80E04345E11F2DB /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
7D0CBC76C80E04345E11F2DB /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||||
7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactoryProtocol.swift; sourceTree = "<group>"; };
|
7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactoryProtocol.swift; sourceTree = "<group>"; };
|
||||||
7D39AF1F659923D77778511E /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
7D39AF1F659923D77778511E /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
@@ -2262,6 +2269,7 @@
|
|||||||
B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = "<group>"; };
|
B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = "<group>"; };
|
||||||
B655A536341D2695158C6664 /* AuthenticationClientBuilderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationClientBuilderFactory.swift; sourceTree = "<group>"; };
|
B655A536341D2695158C6664 /* AuthenticationClientBuilderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationClientBuilderFactory.swift; sourceTree = "<group>"; };
|
||||||
B68B31232312AFC844440BFE /* DeclineAndBlockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenModels.swift; sourceTree = "<group>"; };
|
B68B31232312AFC844440BFE /* DeclineAndBlockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenModels.swift; sourceTree = "<group>"; };
|
||||||
|
B69AEA8755382DB34892FB7B /* ThreadTimelineScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTimelineScreenModels.swift; sourceTree = "<group>"; };
|
||||||
B6A293D06BAB2B7A17D9314B /* VoiceMessageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomTimelineView.swift; sourceTree = "<group>"; };
|
B6A293D06BAB2B7A17D9314B /* VoiceMessageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B6C585CE1F721A2770C70D47 /* TimelineControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineControllerProtocol.swift; sourceTree = "<group>"; };
|
B6C585CE1F721A2770C70D47 /* TimelineControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineControllerProtocol.swift; sourceTree = "<group>"; };
|
||||||
B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHooks.swift; sourceTree = "<group>"; };
|
B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHooks.swift; sourceTree = "<group>"; };
|
||||||
@@ -2289,6 +2297,7 @@
|
|||||||
BB6ED50FE104992419310EEB /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = "<group>"; };
|
BB6ED50FE104992419310EEB /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = "<group>"; };
|
||||||
BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenCoordinator.swift; sourceTree = "<group>"; };
|
BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
BBEC57C204D77908E355EF42 /* AudioRecorderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderProtocol.swift; sourceTree = "<group>"; };
|
BBEC57C204D77908E355EF42 /* AudioRecorderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
BC2E7CEE6E9314FD69FE0ED9 /* ThreadTimelineScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTimelineScreen.swift; sourceTree = "<group>"; };
|
||||||
BC51BF90469412ABDE658CDD /* portrait_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = portrait_test_image.jpg; sourceTree = "<group>"; };
|
BC51BF90469412ABDE658CDD /* portrait_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = portrait_test_image.jpg; sourceTree = "<group>"; };
|
||||||
BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; };
|
BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; };
|
||||||
BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = "<group>"; };
|
BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = "<group>"; };
|
||||||
@@ -2330,6 +2339,7 @@
|
|||||||
C33B3F17996DFDF5F0181512 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
|
C33B3F17996DFDF5F0181512 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
|
||||||
C352359663A0E52BA20761EE /* LoadableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableImage.swift; sourceTree = "<group>"; };
|
C352359663A0E52BA20761EE /* LoadableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableImage.swift; sourceTree = "<group>"; };
|
||||||
C4756240773D26AB74C22668 /* OrientationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationManagerProtocol.swift; sourceTree = "<group>"; };
|
C4756240773D26AB74C22668 /* OrientationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationManagerProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
C4C1C19A4BE46EDE1411ECCE /* ThreadTimelineScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTimelineScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
C4CD503F5E0938FE53C7C6E7 /* UserDetailsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenCoordinator.swift; sourceTree = "<group>"; };
|
C4CD503F5E0938FE53C7C6E7 /* UserDetailsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
C537DE821FED94D23467B6C4 /* PillUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillUtilities.swift; sourceTree = "<group>"; };
|
C537DE821FED94D23467B6C4 /* PillUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillUtilities.swift; sourceTree = "<group>"; };
|
||||||
@@ -4977,6 +4987,18 @@
|
|||||||
path = AppLock;
|
path = AppLock;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
9688AEF931DE2C71683ACBC7 /* ThreadTimelineScreen */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
73FEE625AB52042049DB9268 /* ThreadTimelineScreenCoordinator.swift */,
|
||||||
|
B69AEA8755382DB34892FB7B /* ThreadTimelineScreenModels.swift */,
|
||||||
|
7C9ACDD96F36510C1FC0836B /* ThreadTimelineScreenViewModel.swift */,
|
||||||
|
C4C1C19A4BE46EDE1411ECCE /* ThreadTimelineScreenViewModelProtocol.swift */,
|
||||||
|
A55B1545241E40A06958DDF7 /* View */,
|
||||||
|
);
|
||||||
|
path = ThreadTimelineScreen;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
99B9B46F2D621380428E68F7 /* ElementX */ = {
|
99B9B46F2D621380428E68F7 /* ElementX */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -5120,6 +5142,14 @@
|
|||||||
path = UnitTests;
|
path = UnitTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A55B1545241E40A06958DDF7 /* View */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BC2E7CEE6E9314FD69FE0ED9 /* ThreadTimelineScreen.swift */,
|
||||||
|
);
|
||||||
|
path = View;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A6AA0A048CAE428A5CA4CBBB /* LayoutTests */ = {
|
A6AA0A048CAE428A5CA4CBBB /* LayoutTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -5814,6 +5844,7 @@
|
|||||||
C59BA103987B953BA374509F /* SecurityAndPrivacyScreen */,
|
C59BA103987B953BA374509F /* SecurityAndPrivacyScreen */,
|
||||||
70B74A432C241E56A7ACE610 /* Settings */,
|
70B74A432C241E56A7ACE610 /* Settings */,
|
||||||
EC4545C7E37E8294D3FE6800 /* StartChatScreen */,
|
EC4545C7E37E8294D3FE6800 /* StartChatScreen */,
|
||||||
|
9688AEF931DE2C71683ACBC7 /* ThreadTimelineScreen */,
|
||||||
15D44FCA9475E660B7F56DB9 /* Timeline */,
|
15D44FCA9475E660B7F56DB9 /* Timeline */,
|
||||||
93C7520ED23C9598BB144DBB /* UserProfileScreen */,
|
93C7520ED23C9598BB144DBB /* UserProfileScreen */,
|
||||||
);
|
);
|
||||||
@@ -7688,6 +7719,11 @@
|
|||||||
53F1196F9C69512306A2693F /* TextRoomTimelineItemContent.swift in Sources */,
|
53F1196F9C69512306A2693F /* TextRoomTimelineItemContent.swift in Sources */,
|
||||||
D8CFA0EE46376F9FF04EEE45 /* TextRoomTimelineView.swift in Sources */,
|
D8CFA0EE46376F9FF04EEE45 /* TextRoomTimelineView.swift in Sources */,
|
||||||
71643093F87153F633A1B025 /* ThreadDecorator.swift in Sources */,
|
71643093F87153F633A1B025 /* ThreadDecorator.swift in Sources */,
|
||||||
|
5EB116B58533C9A0EBA22717 /* ThreadTimelineScreen.swift in Sources */,
|
||||||
|
09693596AA3CA4E598C0D2F1 /* ThreadTimelineScreenCoordinator.swift in Sources */,
|
||||||
|
A46B93EA564F539CEC252ECC /* ThreadTimelineScreenModels.swift in Sources */,
|
||||||
|
738288EAEE235CAC0893AB9E /* ThreadTimelineScreenViewModel.swift in Sources */,
|
||||||
|
87E8C31FCF9276F15CB0B408 /* ThreadTimelineScreenViewModelProtocol.swift in Sources */,
|
||||||
2AED12987603157C32C2114D /* TimelineBubbleLayout.swift in Sources */,
|
2AED12987603157C32C2114D /* TimelineBubbleLayout.swift in Sources */,
|
||||||
2BFA4C6D5B3D327B02C66AB0 /* TimelineController.swift in Sources */,
|
2BFA4C6D5B3D327B02C66AB0 /* TimelineController.swift in Sources */,
|
||||||
7DCFC31B49BDD2BC32184E58 /* TimelineControllerFactory.swift in Sources */,
|
7DCFC31B49BDD2BC32184E58 /* TimelineControllerFactory.swift in Sources */,
|
||||||
|
|||||||
@@ -302,6 +302,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (_, .dismissFlow):
|
case (_, .dismissFlow):
|
||||||
return .complete
|
return .complete
|
||||||
|
|
||||||
|
case (_, .presentThread(let itemID)):
|
||||||
|
return .thread(itemID: itemID)
|
||||||
|
case (_, .dismissThread):
|
||||||
|
return .room
|
||||||
|
|
||||||
case (.initial, .presentRoomDetails):
|
case (.initial, .presentRoomDetails):
|
||||||
return .roomDetails(isRoot: true)
|
return .roomDetails(isRoot: true)
|
||||||
case (.room, .presentRoomDetails):
|
case (.room, .presentRoomDetails):
|
||||||
@@ -462,6 +467,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
case (_, .dismissFlow, .complete):
|
case (_, .dismissFlow, .complete):
|
||||||
dismissFlow(animated: animated)
|
dismissFlow(animated: animated)
|
||||||
|
|
||||||
|
case (.room, .presentThread(let itemID), .thread):
|
||||||
|
Task { await self.presentThread(itemID: itemID) }
|
||||||
|
case (.thread, .dismissThread, .room):
|
||||||
|
break
|
||||||
|
|
||||||
case (.initial, .presentRoomDetails, .roomDetails(let isRoot)),
|
case (.initial, .presentRoomDetails, .roomDetails(let isRoot)),
|
||||||
(.room, .presentRoomDetails, .roomDetails(let isRoot)),
|
(.room, .presentRoomDetails, .roomDetails(let isRoot)),
|
||||||
@@ -618,7 +628,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentDeclineAndBlockScreen(userID: userID)
|
presentDeclineAndBlockScreen(userID: userID)
|
||||||
case (.declineAndBlockScreen, .dismissDeclineAndBlockScreen, .joinRoomScreen):
|
case (.declineAndBlockScreen, .dismissDeclineAndBlockScreen, .joinRoomScreen):
|
||||||
break
|
break
|
||||||
|
|
||||||
// Child flow
|
// Child flow
|
||||||
case (_, .startChildFlow(let roomID, let via, let entryPoint), .presentingChild):
|
case (_, .startChildFlow(let roomID, let via, let entryPoint), .presentingChild):
|
||||||
Task { await self.startChildFlow(for: roomID, via: via, entryPoint: entryPoint) }
|
Task { await self.startChildFlow(for: roomID, via: via, entryPoint: entryPoint) }
|
||||||
@@ -782,6 +792,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.presentResolveSendFailure(failure: failure, sendHandle: sendHandle))
|
stateMachine.tryEvent(.presentResolveSendFailure(failure: failure, sendHandle: sendHandle))
|
||||||
case .presentKnockRequestsList:
|
case .presentKnockRequestsList:
|
||||||
stateMachine.tryEvent(.presentKnockRequestsListScreen)
|
stateMachine.tryEvent(.presentKnockRequestsListScreen)
|
||||||
|
case .presentThread(let itemID):
|
||||||
|
stateMachine.tryEvent(.presentThread(itemID: itemID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -789,6 +801,41 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
return coordinator
|
return coordinator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentThread(itemID: TimelineItemIdentifier) async {
|
||||||
|
showLoadingIndicator()
|
||||||
|
defer { hideLoadingIndicator() }
|
||||||
|
|
||||||
|
let timelineItemFactory = RoomTimelineItemFactory(userID: userSession.clientProxy.userID,
|
||||||
|
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||||
|
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userSession.clientProxy.userID))
|
||||||
|
|
||||||
|
guard let eventID = itemID.eventID else {
|
||||||
|
fatalError("Invalid thread event ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard case let .success(timelineController) = await timelineControllerFactory.buildThreadTimelineController(eventID: eventID,
|
||||||
|
roomProxy: roomProxy,
|
||||||
|
timelineItemFactory: timelineItemFactory,
|
||||||
|
mediaProvider: userSession.mediaProvider) else {
|
||||||
|
MXLog.error("Failed presenting media timeline")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let coordinator = ThreadTimelineScreenCoordinator(parameters: .init(roomProxy: roomProxy,
|
||||||
|
timelineController: timelineController,
|
||||||
|
mediaProvider: userSession.mediaProvider,
|
||||||
|
mediaPlayerProvider: MediaPlayerProvider(),
|
||||||
|
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
|
||||||
|
appMediator: appMediator,
|
||||||
|
emojiProvider: emojiProvider,
|
||||||
|
timelineControllerFactory: timelineControllerFactory,
|
||||||
|
clientProxy: userSession.clientProxy))
|
||||||
|
|
||||||
|
navigationStackCoordinator.push(coordinator) { [weak self] in
|
||||||
|
self?.stateMachine.tryEvent(.dismissThread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func presentJoinRoomScreen(via: [String], animated: Bool) {
|
private func presentJoinRoomScreen(via: [String], animated: Bool) {
|
||||||
let coordinator = JoinRoomScreenCoordinator(parameters: .init(roomID: roomID,
|
let coordinator = JoinRoomScreenCoordinator(parameters: .init(roomID: roomID,
|
||||||
via: via,
|
via: via,
|
||||||
@@ -1747,6 +1794,7 @@ private extension RoomFlowCoordinator {
|
|||||||
case initial
|
case initial
|
||||||
case joinRoomScreen
|
case joinRoomScreen
|
||||||
case room
|
case room
|
||||||
|
case thread(itemID: TimelineItemIdentifier)
|
||||||
case roomDetails(isRoot: Bool)
|
case roomDetails(isRoot: Bool)
|
||||||
case roomDetailsEditScreen
|
case roomDetailsEditScreen
|
||||||
case notificationSettings
|
case notificationSettings
|
||||||
@@ -1790,6 +1838,9 @@ private extension RoomFlowCoordinator {
|
|||||||
case presentRoom(presentationAction: PresentationAction?)
|
case presentRoom(presentationAction: PresentationAction?)
|
||||||
case dismissFlow
|
case dismissFlow
|
||||||
|
|
||||||
|
case presentThread(itemID: TimelineItemIdentifier)
|
||||||
|
case dismissThread
|
||||||
|
|
||||||
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||||
case dismissReportContent
|
case dismissReportContent
|
||||||
|
|
||||||
|
|||||||
@@ -6438,6 +6438,76 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
|
|||||||
return timelineFocusedOnEventEventIDNumberOfEventsReturnValue
|
return timelineFocusedOnEventEventIDNumberOfEventsReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - threadTimeline
|
||||||
|
|
||||||
|
var threadTimelineEventIDUnderlyingCallsCount = 0
|
||||||
|
var threadTimelineEventIDCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return threadTimelineEventIDUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = threadTimelineEventIDUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
threadTimelineEventIDUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
threadTimelineEventIDUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var threadTimelineEventIDCalled: Bool {
|
||||||
|
return threadTimelineEventIDCallsCount > 0
|
||||||
|
}
|
||||||
|
var threadTimelineEventIDReceivedEventID: String?
|
||||||
|
var threadTimelineEventIDReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var threadTimelineEventIDUnderlyingReturnValue: Result<TimelineProxyProtocol, RoomProxyError>!
|
||||||
|
var threadTimelineEventIDReturnValue: Result<TimelineProxyProtocol, RoomProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return threadTimelineEventIDUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<TimelineProxyProtocol, RoomProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = threadTimelineEventIDUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
threadTimelineEventIDUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
threadTimelineEventIDUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var threadTimelineEventIDClosure: ((String) async -> Result<TimelineProxyProtocol, RoomProxyError>)?
|
||||||
|
|
||||||
|
func threadTimeline(eventID: String) async -> Result<TimelineProxyProtocol, RoomProxyError> {
|
||||||
|
threadTimelineEventIDCallsCount += 1
|
||||||
|
threadTimelineEventIDReceivedEventID = eventID
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.threadTimelineEventIDReceivedInvocations.append(eventID)
|
||||||
|
}
|
||||||
|
if let threadTimelineEventIDClosure = threadTimelineEventIDClosure {
|
||||||
|
return await threadTimelineEventIDClosure(eventID)
|
||||||
|
} else {
|
||||||
|
return threadTimelineEventIDReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - messageFilteredTimeline
|
//MARK: - messageFilteredTimeline
|
||||||
|
|
||||||
var messageFilteredTimelineFocusAllowedMessageTypesPresentationUnderlyingCallsCount = 0
|
var messageFilteredTimelineFocusAllowedMessageTypesPresentationUnderlyingCallsCount = 0
|
||||||
@@ -14969,6 +15039,76 @@ class TimelineControllerFactoryMock: TimelineControllerFactoryProtocol, @uncheck
|
|||||||
return buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue
|
return buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - buildThreadTimelineController
|
||||||
|
|
||||||
|
var buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
|
||||||
|
var buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderCalled: Bool {
|
||||||
|
return buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderCallsCount > 0
|
||||||
|
}
|
||||||
|
var buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (eventID: String, roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
|
||||||
|
var buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(eventID: String, roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
|
||||||
|
|
||||||
|
var buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>!
|
||||||
|
var buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderReturnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<TimelineControllerProtocol, TimelineFactoryControllerError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderClosure: ((String, JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError>)?
|
||||||
|
|
||||||
|
func buildThreadTimelineController(eventID: String, roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError> {
|
||||||
|
buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
|
||||||
|
buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (eventID: eventID, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations.append((eventID: eventID, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
|
||||||
|
}
|
||||||
|
if let buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderClosure = buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderClosure {
|
||||||
|
return await buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderClosure(eventID, roomProxy, timelineItemFactory, mediaProvider)
|
||||||
|
} else {
|
||||||
|
return buildThreadTimelineControllerEventIDRoomProxyTimelineItemFactoryMediaProviderReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - buildPinnedEventsTimelineController
|
//MARK: - buildPinnedEventsTimelineController
|
||||||
|
|
||||||
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
|
var buildPinnedEventsTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
|
||||||
|
|||||||
+2
-2
@@ -81,7 +81,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
|||||||
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
||||||
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
|
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
|
||||||
.displaySenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
|
.displaySenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
|
||||||
.composer, .hasScrolled, .viewInRoomTimeline:
|
.displayThread, .composer, .hasScrolled, .viewInRoomTimeline:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
|||||||
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
||||||
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
|
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
|
||||||
.displaySenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
|
.displaySenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
|
||||||
.composer, .hasScrolled, .viewInRoomTimeline:
|
.displayThread, .composer, .hasScrolled, .viewInRoomTimeline:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -91,7 +91,7 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
|||||||
// These other actions will not be handled in this view
|
// These other actions will not be handled in this view
|
||||||
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
||||||
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
|
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
|
||||||
.displayResolveSendFailure, .composer, .hasScrolled:
|
.displayResolveSendFailure, .displayThread, .composer, .hasScrolled:
|
||||||
// These actions are not handled in this coordinator
|
// These actions are not handled in this coordinator
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ enum RoomScreenCoordinatorAction {
|
|||||||
case presentPinnedEventsTimeline
|
case presentPinnedEventsTimeline
|
||||||
case presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
|
case presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
|
||||||
case presentKnockRequestsList
|
case presentKnockRequestsList
|
||||||
|
case presentThread(itemID: TimelineItemIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
final class RoomScreenCoordinator: CoordinatorProtocol {
|
final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -144,6 +145,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.presentLocationViewer(body: body, geoURI: geoURI, description: description))
|
actionsSubject.send(.presentLocationViewer(body: body, geoURI: geoURI, description: description))
|
||||||
case .displayResolveSendFailure(let failure, let sendHandle):
|
case .displayResolveSendFailure(let failure, let sendHandle):
|
||||||
actionsSubject.send(.presentResolveSendFailure(failure: failure, sendHandle: sendHandle))
|
actionsSubject.send(.presentResolveSendFailure(failure: failure, sendHandle: sendHandle))
|
||||||
|
case .displayThread(let itemID):
|
||||||
|
actionsSubject.send(.presentThread(itemID: itemID))
|
||||||
case .composer(let action):
|
case .composer(let action):
|
||||||
composerViewModel.process(timelineAction: action)
|
composerViewModel.process(timelineAction: action)
|
||||||
case .hasScrolled(direction: let direction):
|
case .hasScrolled(direction: let direction):
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ThreadTimelineScreenCoordinatorParameters {
|
||||||
|
let roomProxy: JoinedRoomProxyProtocol
|
||||||
|
let timelineController: TimelineControllerProtocol
|
||||||
|
let mediaProvider: MediaProviderProtocol
|
||||||
|
let mediaPlayerProvider: MediaPlayerProviderProtocol
|
||||||
|
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
||||||
|
let appMediator: AppMediatorProtocol
|
||||||
|
let emojiProvider: EmojiProviderProtocol
|
||||||
|
let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||||
|
let clientProxy: ClientProxyProtocol
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ThreadTimelineScreenCoordinatorAction {
|
||||||
|
case dismiss
|
||||||
|
case displayUser(userID: String)
|
||||||
|
case presentLocationViewer(geoURI: GeoURI, description: String?)
|
||||||
|
case displayMessageForwarding(forwardingItem: MessageForwardingItem)
|
||||||
|
case displayRoomScreenWithFocussedPin(eventID: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ThreadTimelineScreenCoordinator: CoordinatorProtocol {
|
||||||
|
private let parameters: ThreadTimelineScreenCoordinatorParameters
|
||||||
|
private let viewModel: ThreadTimelineScreenViewModelProtocol
|
||||||
|
private let timelineViewModel: TimelineViewModelProtocol
|
||||||
|
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private let actionsSubject: PassthroughSubject<ThreadTimelineScreenCoordinatorAction, Never> = .init()
|
||||||
|
var actions: AnyPublisher<ThreadTimelineScreenCoordinatorAction, Never> {
|
||||||
|
actionsSubject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(parameters: ThreadTimelineScreenCoordinatorParameters) {
|
||||||
|
self.parameters = parameters
|
||||||
|
|
||||||
|
viewModel = ThreadTimelineScreenViewModel()
|
||||||
|
timelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
||||||
|
timelineController: parameters.timelineController,
|
||||||
|
mediaProvider: parameters.mediaProvider,
|
||||||
|
mediaPlayerProvider: parameters.mediaPlayerProvider,
|
||||||
|
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
|
||||||
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||||
|
appMediator: parameters.appMediator,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
|
emojiProvider: parameters.emojiProvider,
|
||||||
|
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||||
|
clientProxy: parameters.clientProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
timelineViewModel.actions.sink { [weak self] action in
|
||||||
|
MXLog.info("Coordinator: received timeline view model action: \(action)")
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .displaySenderDetails(let userID):
|
||||||
|
actionsSubject.send(.displayUser(userID: userID))
|
||||||
|
case .displayMessageForwarding(let forwardingItem):
|
||||||
|
actionsSubject.send(.displayMessageForwarding(forwardingItem: forwardingItem))
|
||||||
|
case .displayLocation(_, let geoURI, let description):
|
||||||
|
actionsSubject.send(.presentLocationViewer(geoURI: geoURI, description: description))
|
||||||
|
case .viewInRoomTimeline(let eventID):
|
||||||
|
actionsSubject.send(.displayRoomScreenWithFocussedPin(eventID: eventID))
|
||||||
|
// These other actions will not be handled in this view
|
||||||
|
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
||||||
|
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaPreview,
|
||||||
|
.displayMediaUploadPreviewScreen, .displayResolveSendFailure, .displayThread, .composer, .hasScrolled:
|
||||||
|
// These actions are not handled in this coordinator
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
viewModel.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPresentable() -> AnyView {
|
||||||
|
AnyView(ThreadTimelineScreen(context: viewModel.context, timelineContext: timelineViewModel.context))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum ThreadTimelineScreenViewModelAction { }
|
||||||
|
|
||||||
|
struct ThreadTimelineScreenViewState: BindableState {
|
||||||
|
var bindings = ThreadTimelineScreenViewStateBindings()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadTimelineScreenViewStateBindings {
|
||||||
|
/// The view model used to present a QuickLook media preview.
|
||||||
|
var mediaPreviewViewModel: TimelineMediaPreviewViewModel?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ThreadTimelineScreenViewAction { }
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
typealias ThreadTimelineScreenViewModelType = StateStoreViewModel<ThreadTimelineScreenViewState, ThreadTimelineScreenViewAction>
|
||||||
|
|
||||||
|
class ThreadTimelineScreenViewModel: ThreadTimelineScreenViewModelType, ThreadTimelineScreenViewModelProtocol {
|
||||||
|
private let actionsSubject: PassthroughSubject<ThreadTimelineScreenViewModelAction, Never> = .init()
|
||||||
|
var actionsPublisher: AnyPublisher<ThreadTimelineScreenViewModelAction, Never> {
|
||||||
|
actionsSubject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(initialViewState: ThreadTimelineScreenViewState())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
override func process(viewAction: ThreadTimelineScreenViewAction) { }
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
// Work around QLPreviewController dismissal issues, see the InteractiveQuickLookModifier.
|
||||||
|
state.bindings.mediaPreviewViewModel = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
protocol ThreadTimelineScreenViewModelProtocol {
|
||||||
|
var actionsPublisher: AnyPublisher<ThreadTimelineScreenViewModelAction, Never> { get }
|
||||||
|
var context: ThreadTimelineScreenViewModelType.Context { get }
|
||||||
|
|
||||||
|
func stop()
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ThreadTimelineScreen: View {
|
||||||
|
@ObservedObject var context: ThreadTimelineScreenViewModel.Context
|
||||||
|
@ObservedObject var timelineContext: TimelineViewModel.Context
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.navigationTitle("Thread")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.background(.compound.bgCanvasDefault)
|
||||||
|
.interactiveDismissDisabled()
|
||||||
|
.timelineMediaPreview(viewModel: $context.mediaPreviewViewModel)
|
||||||
|
.sheet(item: $timelineContext.manageMemberViewModel) {
|
||||||
|
ManageRoomMemberSheetView(context: $0.context)
|
||||||
|
}
|
||||||
|
.sheet(item: $timelineContext.debugInfo) { TimelineItemDebugView(info: $0) }
|
||||||
|
.sheet(item: $timelineContext.actionMenuInfo) { info in
|
||||||
|
let actions = TimelineItemMenuActionProvider(timelineItem: info.item,
|
||||||
|
canCurrentUserRedactSelf: timelineContext.viewState.canCurrentUserRedactSelf,
|
||||||
|
canCurrentUserRedactOthers: timelineContext.viewState.canCurrentUserRedactOthers,
|
||||||
|
canCurrentUserPin: timelineContext.viewState.canCurrentUserPin,
|
||||||
|
pinnedEventIDs: timelineContext.viewState.pinnedEventIDs,
|
||||||
|
isDM: timelineContext.viewState.isDirectOneToOneRoom,
|
||||||
|
isViewSourceEnabled: timelineContext.viewState.isViewSourceEnabled,
|
||||||
|
timelineKind: timelineContext.viewState.timelineKind,
|
||||||
|
emojiProvider: timelineContext.viewState.emojiProvider)
|
||||||
|
.makeActions()
|
||||||
|
if let actions {
|
||||||
|
TimelineItemMenu(item: info.item, actions: actions)
|
||||||
|
.environmentObject(timelineContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var content: some View {
|
||||||
|
TimelineView()
|
||||||
|
.id(timelineContext.viewState.roomID)
|
||||||
|
.environmentObject(timelineContext)
|
||||||
|
.environment(\.focussedEventID, timelineContext.viewState.timelineState.focussedEvent?.eventID)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -554,7 +554,7 @@ class TimelineInteractionHandler {
|
|||||||
case .pinned:
|
case .pinned:
|
||||||
newTimelineFocus = .pinned
|
newTimelineFocus = .pinned
|
||||||
newTimelinePresentation = .pinnedEventsScreen
|
newTimelinePresentation = .pinnedEventsScreen
|
||||||
case .media:
|
case .media, .thread:
|
||||||
break // We don't need to create a new timeline as it is already filtered.
|
break // We don't need to create a new timeline as it is already filtered.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ enum TimelineViewModelAction {
|
|||||||
case displayMediaPreview(TimelineMediaPreviewViewModel)
|
case displayMediaPreview(TimelineMediaPreviewViewModel)
|
||||||
case displayLocation(body: String, geoURI: GeoURI, description: String?)
|
case displayLocation(body: String, geoURI: GeoURI, description: String?)
|
||||||
case displayResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
|
case displayResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
|
||||||
|
case displayThread(itemID: TimelineItemIdentifier)
|
||||||
case composer(action: TimelineComposerAction)
|
case composer(action: TimelineComposerAction)
|
||||||
case hasScrolled(direction: ScrollDirection)
|
case hasScrolled(direction: ScrollDirection)
|
||||||
case viewInRoomTimeline(eventID: String)
|
case viewInRoomTimeline(eventID: String)
|
||||||
|
|||||||
@@ -191,8 +191,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
displayReactionSummary(for: itemID, selectedKey: key)
|
displayReactionSummary(for: itemID, selectedKey: key)
|
||||||
case .displayReadReceipts(let itemID):
|
case .displayReadReceipts(let itemID):
|
||||||
displayReadReceipts(for: itemID)
|
displayReadReceipts(for: itemID)
|
||||||
case .displayThread:
|
case .displayThread(let itemID):
|
||||||
break
|
actionsSubject.send(.displayThread(itemID: itemID))
|
||||||
case .handlePasteOrDrop(let provider):
|
case .handlePasteOrDrop(let provider):
|
||||||
timelineInteractionHandler.handlePasteOrDrop(provider)
|
timelineInteractionHandler.handlePasteOrDrop(provider)
|
||||||
case .handlePollAction(let pollAction):
|
case .handlePollAction(let pollAction):
|
||||||
|
|||||||
+1
-1
@@ -111,7 +111,7 @@ struct TimelineItemMenuActionProvider {
|
|||||||
actions.append(.save)
|
actions.append(.save)
|
||||||
actions = actions.filter(\.canAppearInMediaDetails)
|
actions = actions.filter(\.canAppearInMediaDetails)
|
||||||
secondaryActions = secondaryActions.filter(\.canAppearInMediaDetails)
|
secondaryActions = secondaryActions.filter(\.canAppearInMediaDetails)
|
||||||
case .live, .detached:
|
case .live, .detached, .thread:
|
||||||
break // viewInRoomTimeline is the only non-room item and was added conditionally.
|
break // viewInRoomTimeline is the only non-room item and was added conditionally.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
.onTapGesture { }
|
.onTapGesture { }
|
||||||
}
|
}
|
||||||
|
|
||||||
if let threadSummary = timelineItem.properties.threadSummary {
|
if context.viewState.timelineKind != .thread, let threadSummary = timelineItem.properties.threadSummary {
|
||||||
TimelineThreadSummaryView(threadSummary: threadSummary) {
|
TimelineThreadSummaryView(threadSummary: threadSummary) {
|
||||||
context.send(viewAction: .displayThread(itemID: timelineItem.id))
|
context.send(viewAction: .displayThread(itemID: timelineItem.id))
|
||||||
}
|
}
|
||||||
@@ -177,13 +177,13 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var contentWithReply: some View {
|
var contentWithReply: some View {
|
||||||
TimelineBubbleLayout(spacing: 8) {
|
TimelineBubbleLayout(spacing: 8) {
|
||||||
if timelineItem.properties.isThreaded {
|
if context.viewState.timelineKind != .thread, timelineItem.properties.isThreaded {
|
||||||
ThreadDecorator()
|
ThreadDecorator()
|
||||||
.padding(.leading, 4)
|
.padding(.leading, 4)
|
||||||
.layoutPriority(TimelineBubbleLayout.Priority.regularText)
|
.layoutPriority(TimelineBubbleLayout.Priority.regularText)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let replyDetails = timelineItem.properties.replyDetails {
|
if shouldShowReplyDetails, let replyDetails = timelineItem.properties.replyDetails {
|
||||||
// The rendered reply bubble with a greedy width. The custom layout prevents
|
// The rendered reply bubble with a greedy width. The custom layout prevents
|
||||||
// the infinite width from increasing the overall width of the view.
|
// the infinite width from increasing the overall width of the view.
|
||||||
|
|
||||||
@@ -212,6 +212,10 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var shouldShowReplyDetails: Bool {
|
||||||
|
!timelineItem.properties.isThreaded || (timelineItem.properties.isThreaded && context.viewState.timelineKind != .thread)
|
||||||
|
}
|
||||||
|
|
||||||
private var messageBubbleTopPadding: CGFloat {
|
private var messageBubbleTopPadding: CGFloat {
|
||||||
guard timelineItem.isOutgoing || isDirectOneToOneRoom else { return 0 }
|
guard timelineItem.isOutgoing || isDirectOneToOneRoom else { return 0 }
|
||||||
return timelineGroupStyle == .single || timelineGroupStyle == .first ? 8 : 0
|
return timelineGroupStyle == .single || timelineGroupStyle == .first ? 8 : 0
|
||||||
|
|||||||
@@ -190,6 +190,24 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func threadTimeline(eventID: String) async -> Result<TimelineProxyProtocol, RoomProxyError> {
|
||||||
|
do {
|
||||||
|
let sdkTimeline = try await room.timelineWithConfiguration(configuration: .init(focus: .thread(rootEventId: eventID, numEvents: 20),
|
||||||
|
filter: .all,
|
||||||
|
internalIdPrefix: UUID().uuidString,
|
||||||
|
dateDividerMode: .daily,
|
||||||
|
trackReadReceipts: true))
|
||||||
|
|
||||||
|
let timeline = TimelineProxy(timeline: sdkTimeline, kind: .thread)
|
||||||
|
await timeline.subscribeForUpdates()
|
||||||
|
|
||||||
|
return .success(timeline)
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Unexpected error: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func messageFilteredTimeline(focus: TimelineFocus,
|
func messageFilteredTimeline(focus: TimelineFocus,
|
||||||
allowedMessageTypes: [TimelineAllowedMessageType],
|
allowedMessageTypes: [TimelineAllowedMessageType],
|
||||||
presentation: TimelineKind.MediaPresentation) async -> Result<any TimelineProxyProtocol, RoomProxyError> {
|
presentation: TimelineKind.MediaPresentation) async -> Result<any TimelineProxyProtocol, RoomProxyError> {
|
||||||
@@ -197,6 +215,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
let rustFocus: MatrixRustSDK.TimelineFocus = switch focus {
|
let rustFocus: MatrixRustSDK.TimelineFocus = switch focus {
|
||||||
case .live: .live
|
case .live: .live
|
||||||
case .eventID(let eventID): .event(eventId: eventID, numContextEvents: 100)
|
case .eventID(let eventID): .event(eventId: eventID, numContextEvents: 100)
|
||||||
|
case .thread(let eventID): .thread(rootEventId: eventID, numEvents: 20)
|
||||||
case .pinned: .pinnedEvents(maxEventsToLoad: 100, maxConcurrentRequests: 10)
|
case .pinned: .pinnedEvents(maxEventsToLoad: 100, maxConcurrentRequests: 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
|||||||
|
|
||||||
func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result<TimelineProxyProtocol, RoomProxyError>
|
func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result<TimelineProxyProtocol, RoomProxyError>
|
||||||
|
|
||||||
|
func threadTimeline(eventID: String) async -> Result<TimelineProxyProtocol, RoomProxyError>
|
||||||
|
|
||||||
func messageFilteredTimeline(focus: TimelineFocus,
|
func messageFilteredTimeline(focus: TimelineFocus,
|
||||||
allowedMessageTypes: [TimelineAllowedMessageType],
|
allowedMessageTypes: [TimelineAllowedMessageType],
|
||||||
presentation: TimelineKind.MediaPresentation) async -> Result<TimelineProxyProtocol, RoomProxyError>
|
presentation: TimelineKind.MediaPresentation) async -> Result<TimelineProxyProtocol, RoomProxyError>
|
||||||
|
|||||||
@@ -21,6 +21,23 @@ struct TimelineControllerFactory: TimelineControllerFactoryProtocol {
|
|||||||
appSettings: ServiceLocator.shared.settings)
|
appSettings: ServiceLocator.shared.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildThreadTimelineController(eventID: String,
|
||||||
|
roomProxy: JoinedRoomProxyProtocol,
|
||||||
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
|
mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError> {
|
||||||
|
switch await roomProxy.threadTimeline(eventID: eventID) {
|
||||||
|
case .success(let timelineProxy):
|
||||||
|
return .success(TimelineController(roomProxy: roomProxy,
|
||||||
|
timelineProxy: timelineProxy,
|
||||||
|
initialFocussedEventID: nil,
|
||||||
|
timelineItemFactory: timelineItemFactory,
|
||||||
|
mediaProvider: mediaProvider,
|
||||||
|
appSettings: ServiceLocator.shared.settings))
|
||||||
|
case .failure(let error):
|
||||||
|
return .failure(.roomProxyError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol? {
|
mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol? {
|
||||||
|
|||||||
+5
@@ -19,6 +19,11 @@ protocol TimelineControllerFactoryProtocol {
|
|||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
mediaProvider: MediaProviderProtocol) -> TimelineControllerProtocol
|
mediaProvider: MediaProviderProtocol) -> TimelineControllerProtocol
|
||||||
|
|
||||||
|
func buildThreadTimelineController(eventID: String,
|
||||||
|
roomProxy: JoinedRoomProxyProtocol,
|
||||||
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
|
mediaProvider: MediaProviderProtocol) async -> Result<TimelineControllerProtocol, TimelineFactoryControllerError>
|
||||||
|
|
||||||
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
func buildPinnedEventsTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol?
|
mediaProvider: MediaProviderProtocol) async -> TimelineControllerProtocol?
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
switch kind {
|
switch kind {
|
||||||
case .live:
|
case .live:
|
||||||
return await paginateBackwardsOnLive(requestSize: requestSize)
|
return await paginateBackwardsOnLive(requestSize: requestSize)
|
||||||
case .detached, .media:
|
case .detached, .media, .thread:
|
||||||
return await focussedPaginate(.backwards, requestSize: requestSize)
|
return await focussedPaginate(.backwards, requestSize: requestSize)
|
||||||
case .pinned:
|
case .pinned:
|
||||||
return .success(())
|
return .success(())
|
||||||
@@ -596,7 +596,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
MXLog.error("Failed to subscribe to back pagination status with error: \(error)")
|
MXLog.error("Failed to subscribe to back pagination status with error: \(error)")
|
||||||
}
|
}
|
||||||
forwardPaginationStatusSubject.send(.timelineEndReached)
|
forwardPaginationStatusSubject.send(.timelineEndReached)
|
||||||
case .detached:
|
case .detached, .thread:
|
||||||
// Detached timelines don't support observation, set the initial state ourself.
|
// Detached timelines don't support observation, set the initial state ourself.
|
||||||
backPaginationStatusSubject.send(.idle)
|
backPaginationStatusSubject.send(.idle)
|
||||||
forwardPaginationStatusSubject.send(.idle)
|
forwardPaginationStatusSubject.send(.idle)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ enum TimelineKind: Equatable {
|
|||||||
case live
|
case live
|
||||||
case detached
|
case detached
|
||||||
case pinned
|
case pinned
|
||||||
|
case thread
|
||||||
|
|
||||||
enum MediaPresentation { case roomScreenLive, roomScreenDetached, pinnedEventsScreen, mediaFilesScreen }
|
enum MediaPresentation { case roomScreenLive, roomScreenDetached, pinnedEventsScreen, mediaFilesScreen }
|
||||||
case media(MediaPresentation)
|
case media(MediaPresentation)
|
||||||
@@ -21,6 +22,7 @@ enum TimelineKind: Equatable {
|
|||||||
enum TimelineFocus {
|
enum TimelineFocus {
|
||||||
case live
|
case live
|
||||||
case eventID(String)
|
case eventID(String)
|
||||||
|
case thread(eventID: String)
|
||||||
case pinned
|
case pinned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user