mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Fix a potential race condition when redacting a message. (#3061)
* Refactor the timeline item menu action provider. - Move it into its own struct. - Use an item, not an ID so it doesn't randomly change. - Move permissions into the room screen view model. * Use the stable ID when redacting/editing/forwarding a message. Just like we do when fetching the item in the actions menu.
This commit is contained in:
@@ -25,7 +25,6 @@
|
|||||||
01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */; };
|
01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C6624240FFD32B7F0834229 /* IdentityConfirmedScreenViewModel.swift */; };
|
||||||
0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B746EFA112532A7B701FB914 /* RoomNotificationSettingsCustomSectionView.swift */; };
|
0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B746EFA112532A7B701FB914 /* RoomNotificationSettingsCustomSectionView.swift */; };
|
||||||
01B63F1A04A276B39AC17014 /* CallInviteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */; };
|
01B63F1A04A276B39AC17014 /* CallInviteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */; };
|
||||||
020C530986D7B97631877FEF /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */; };
|
|
||||||
020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D7F513A36C9C1951DB44C /* AnalyticsSettingsScreen.swift */; };
|
020F7E70167FB2833266F2F0 /* AnalyticsSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D7F513A36C9C1951DB44C /* AnalyticsSettingsScreen.swift */; };
|
||||||
024E70451A7CD9E4E034D8A9 /* VoiceMessageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D529B976F8B2AA654D923422 /* VoiceMessageRoomTimelineItem.swift */; };
|
024E70451A7CD9E4E034D8A9 /* VoiceMessageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D529B976F8B2AA654D923422 /* VoiceMessageRoomTimelineItem.swift */; };
|
||||||
02A92F8F4538CECDFB4F2607 /* RoomDirectorySearchScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */; };
|
02A92F8F4538CECDFB4F2607 /* RoomDirectorySearchScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */; };
|
||||||
@@ -410,6 +409,7 @@
|
|||||||
61A36B9BB2ADE36CEFF5E98C /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */; };
|
61A36B9BB2ADE36CEFF5E98C /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */; };
|
||||||
62418EA4E3EB597AD184AEB6 /* PillConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */; };
|
62418EA4E3EB597AD184AEB6 /* PillConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */; };
|
||||||
627139A3D79F032BA81E3A53 /* UserSessionFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA29BAE9B0F2D90E57B261C /* UserSessionFlowCoordinatorTests.swift */; };
|
627139A3D79F032BA81E3A53 /* UserSessionFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA29BAE9B0F2D90E57B261C /* UserSessionFlowCoordinatorTests.swift */; };
|
||||||
|
62833C090D599023D92A0424 /* TimelineItemMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8D3544DE3FABD958BA8F19 /* TimelineItemMenu.swift */; };
|
||||||
62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */; };
|
62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */; };
|
||||||
6298AB0906DDD3525CD78C6B /* LoremSwiftum in Frameworks */ = {isa = PBXBuildFile; productRef = 1A6B622CCFDEFB92D9CF1CA5 /* LoremSwiftum */; };
|
6298AB0906DDD3525CD78C6B /* LoremSwiftum in Frameworks */ = {isa = PBXBuildFile; productRef = 1A6B622CCFDEFB92D9CF1CA5 /* LoremSwiftum */; };
|
||||||
62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; };
|
62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; };
|
||||||
@@ -482,6 +482,7 @@
|
|||||||
71AC1CAAC23403FFE847F2C9 /* ComposerToolbarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90514BE9B8ACCBCF0AD2489 /* ComposerToolbarViewModel.swift */; };
|
71AC1CAAC23403FFE847F2C9 /* ComposerToolbarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90514BE9B8ACCBCF0AD2489 /* ComposerToolbarViewModel.swift */; };
|
||||||
71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */; };
|
71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */; };
|
||||||
71C1347F23868324A4F43940 /* NavigationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A22A05E472533ED3C5A31B3 /* NavigationModule.swift */; };
|
71C1347F23868324A4F43940 /* NavigationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A22A05E472533ED3C5A31B3 /* NavigationModule.swift */; };
|
||||||
|
71C532CDC9995236FC1B6EE6 /* TimelineItemMenuActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2751266F17A5BF25DA9227E /* TimelineItemMenuActionProvider.swift */; };
|
||||||
733E2B19AB1FDA3B93293A28 /* AppLockSetupPINScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F275432954C8C6B1B7D966 /* AppLockSetupPINScreen.swift */; };
|
733E2B19AB1FDA3B93293A28 /* AppLockSetupPINScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F275432954C8C6B1B7D966 /* AppLockSetupPINScreen.swift */; };
|
||||||
7354D094A4C59B555F407FA1 /* RustTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542D4F49FABA056DEEEB3400 /* RustTracing.swift */; };
|
7354D094A4C59B555F407FA1 /* RustTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542D4F49FABA056DEEEB3400 /* RustTracing.swift */; };
|
||||||
7361B011A79BF723D8C9782B /* EmojiCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C1A3D524D63815B28FA4D62 /* EmojiCategory.swift */; };
|
7361B011A79BF723D8C9782B /* EmojiCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C1A3D524D63815B28FA4D62 /* EmojiCategory.swift */; };
|
||||||
@@ -566,7 +567,6 @@
|
|||||||
85813D87DDD7F67A46BD9AF7 /* ImageProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E8A8047B50E3607ACD354E /* ImageProviderProtocol.swift */; };
|
85813D87DDD7F67A46BD9AF7 /* ImageProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E8A8047B50E3607ACD354E /* ImageProviderProtocol.swift */; };
|
||||||
858276B19C7C0AD4CA98EA78 /* portrait_test_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = AF042B0FB2EE88977C91E330 /* portrait_test_image.jpg */; };
|
858276B19C7C0AD4CA98EA78 /* portrait_test_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = AF042B0FB2EE88977C91E330 /* portrait_test_image.jpg */; };
|
||||||
8587A53DE8EF94FD796DC375 /* RoomAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEF5FE93A06F563B477F024A /* RoomAvatarImage.swift */; };
|
8587A53DE8EF94FD796DC375 /* RoomAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEF5FE93A06F563B477F024A /* RoomAvatarImage.swift */; };
|
||||||
858C04B62166B5BAFCD20F2D /* TimelineItemMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1BF12A5E7C76777C4BF0F2B /* TimelineItemMenu.swift */; };
|
|
||||||
859E2CA2EDF343BD24DE52EB /* RoomDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */; };
|
859E2CA2EDF343BD24DE52EB /* RoomDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */; };
|
||||||
85F89F3F320F4FADCFFFE68B /* ServerSelectionScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3059CFA00C67D8787273B20 /* ServerSelectionScreenViewModel.swift */; };
|
85F89F3F320F4FADCFFFE68B /* ServerSelectionScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3059CFA00C67D8787273B20 /* ServerSelectionScreenViewModel.swift */; };
|
||||||
864C0D3A4077BF433DBC691F /* PollRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5281C5CDC4A712265A0B5FBF /* PollRoomTimelineItem.swift */; };
|
864C0D3A4077BF433DBC691F /* PollRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5281C5CDC4A712265A0B5FBF /* PollRoomTimelineItem.swift */; };
|
||||||
@@ -751,6 +751,7 @@
|
|||||||
AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */; };
|
AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */; };
|
||||||
AFE2AB612A1460E49578D746 /* JoinRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */; };
|
AFE2AB612A1460E49578D746 /* JoinRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */; };
|
||||||
B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */; };
|
B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */; };
|
||||||
|
B0BA59A46ACCF0A3ECBBB7E0 /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31352FFC2EF2C353CB7EA376 /* TimelineItemMacContextMenu.swift */; };
|
||||||
B0CB16349B96262AA65A04AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; };
|
B0CB16349B96262AA65A04AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; };
|
||||||
B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; };
|
B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; };
|
||||||
B13774779EA19FDD7A35A4A8 /* RoomRolesAndPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */; };
|
B13774779EA19FDD7A35A4A8 /* RoomRolesAndPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */; };
|
||||||
@@ -1039,6 +1040,7 @@
|
|||||||
F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */; };
|
F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */; };
|
||||||
F50A6FCE26714E27FE5495DD /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F23B21CF15F9F4BAA0788B /* PollOptionView.swift */; };
|
F50A6FCE26714E27FE5495DD /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F23B21CF15F9F4BAA0788B /* PollOptionView.swift */; };
|
||||||
F519DE17A3A0F760307B2E6D /* InviteUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */; };
|
F519DE17A3A0F760307B2E6D /* InviteUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */; };
|
||||||
|
F541922A5B28C995E0BDB4E7 /* TimelineItemMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF64B3A815D04325F1980E02 /* TimelineItemMenuAction.swift */; };
|
||||||
F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */; };
|
F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */; };
|
||||||
F5D2270B5021D521C0D22E11 /* FlowCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */; };
|
F5D2270B5021D521C0D22E11 /* FlowCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */; };
|
||||||
F656F92A63D3DC1978D79427 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 290FDEDA4D764B9F7EBE55A9 /* Algorithms */; };
|
F656F92A63D3DC1978D79427 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 290FDEDA4D764B9F7EBE55A9 /* Algorithms */; };
|
||||||
@@ -1351,6 +1353,7 @@
|
|||||||
307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreen.swift; sourceTree = "<group>"; };
|
307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmedScreen.swift; sourceTree = "<group>"; };
|
||||||
309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriver.swift; sourceTree = "<group>"; };
|
309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriver.swift; sourceTree = "<group>"; };
|
||||||
30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerTests.swift; sourceTree = "<group>"; };
|
30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerTests.swift; sourceTree = "<group>"; };
|
||||||
|
31352FFC2EF2C353CB7EA376 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = "<group>"; };
|
||||||
314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceipt.swift; sourceTree = "<group>"; };
|
314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceipt.swift; sourceTree = "<group>"; };
|
||||||
317F41A4B5C4F457AF710666 /* PollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollView.swift; sourceTree = "<group>"; };
|
317F41A4B5C4F457AF710666 /* PollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollView.swift; sourceTree = "<group>"; };
|
||||||
31A6314FDC51DA25712D9A81 /* PillContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContextTests.swift; sourceTree = "<group>"; };
|
31A6314FDC51DA25712D9A81 /* PillContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContextTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -1462,7 +1465,6 @@
|
|||||||
49E45C3DC740D3AB9A47FD32 /* SwipeToReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToReplyView.swift; sourceTree = "<group>"; };
|
49E45C3DC740D3AB9A47FD32 /* SwipeToReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToReplyView.swift; sourceTree = "<group>"; };
|
||||||
49E6066092ED45E36BB306F7 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
49E6066092ED45E36BB306F7 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
|
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
|
||||||
4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = "<group>"; };
|
|
||||||
4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; };
|
4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; };
|
||||||
4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = "<group>"; };
|
4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = "<group>"; };
|
||||||
4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = "<group>"; };
|
4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -1788,9 +1790,9 @@
|
|||||||
A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
A16CD2C62CB7DB78A4238485 /* ReportContentScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenCoordinator.swift; sourceTree = "<group>"; };
|
A16CD2C62CB7DB78A4238485 /* ReportContentScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
A1BF12A5E7C76777C4BF0F2B /* TimelineItemMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenu.swift; sourceTree = "<group>"; };
|
|
||||||
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; };
|
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; };
|
||||||
A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryPicker.swift; sourceTree = "<group>"; };
|
A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryPicker.swift; sourceTree = "<group>"; };
|
||||||
|
A2751266F17A5BF25DA9227E /* TimelineItemMenuActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenuActionProvider.swift; sourceTree = "<group>"; };
|
||||||
A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineView.swift; sourceTree = "<group>"; };
|
A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenContent.swift; sourceTree = "<group>"; };
|
A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenContent.swift; sourceTree = "<group>"; };
|
||||||
A3FBD9C2B9A5479526920399 /* BugReportScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenCoordinator.swift; sourceTree = "<group>"; };
|
A3FBD9C2B9A5479526920399 /* BugReportScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -1845,6 +1847,7 @@
|
|||||||
AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilderTests.swift; sourceTree = "<group>"; };
|
AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilderTests.swift; sourceTree = "<group>"; };
|
||||||
AF042B0FB2EE88977C91E330 /* portrait_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = portrait_test_image.jpg; sourceTree = "<group>"; };
|
AF042B0FB2EE88977C91E330 /* portrait_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = portrait_test_image.jpg; sourceTree = "<group>"; };
|
||||||
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderTests.swift; sourceTree = "<group>"; };
|
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderTests.swift; sourceTree = "<group>"; };
|
||||||
|
AF64B3A815D04325F1980E02 /* TimelineItemMenuAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenuAction.swift; sourceTree = "<group>"; };
|
||||||
AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenModels.swift; sourceTree = "<group>"; };
|
AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenModels.swift; sourceTree = "<group>"; };
|
||||||
AFEF489B8E2450E2BA1A314E /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/SAS.strings; sourceTree = "<group>"; };
|
AFEF489B8E2450E2BA1A314E /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/SAS.strings; sourceTree = "<group>"; };
|
||||||
B0618820D26F9871A4BBB40E /* ComposerToolbarViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModelProtocol.swift; sourceTree = "<group>"; };
|
B0618820D26F9871A4BBB40E /* ComposerToolbarViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -2104,6 +2107,7 @@
|
|||||||
EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = "<group>"; };
|
EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = "<group>"; };
|
||||||
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = "<group>"; };
|
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
EC5D7DA665E1F5F509C994C7 /* ScaledOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledOffsetModifier.swift; sourceTree = "<group>"; };
|
EC5D7DA665E1F5F509C994C7 /* ScaledOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledOffsetModifier.swift; sourceTree = "<group>"; };
|
||||||
|
EC8D3544DE3FABD958BA8F19 /* TimelineItemMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenu.swift; sourceTree = "<group>"; };
|
||||||
ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfiguration.swift; sourceTree = "<group>"; };
|
ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfiguration.swift; sourceTree = "<group>"; };
|
||||||
@@ -2931,6 +2935,17 @@
|
|||||||
path = QRCodeLoginScreen;
|
path = QRCodeLoginScreen;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
3E69187DDC5E781EB96A7BED /* ItemMenu */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
31352FFC2EF2C353CB7EA376 /* TimelineItemMacContextMenu.swift */,
|
||||||
|
EC8D3544DE3FABD958BA8F19 /* TimelineItemMenu.swift */,
|
||||||
|
AF64B3A815D04325F1980E02 /* TimelineItemMenuAction.swift */,
|
||||||
|
A2751266F17A5BF25DA9227E /* TimelineItemMenuActionProvider.swift */,
|
||||||
|
);
|
||||||
|
path = ItemMenu;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
3EA31CC7012EA2A5653DAFC9 /* Fixtures */ = {
|
3EA31CC7012EA2A5653DAFC9 /* Fixtures */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -3787,9 +3802,8 @@
|
|||||||
5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */,
|
5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */,
|
||||||
4552D3466B1453F287223ADA /* SwipeRightAction.swift */,
|
4552D3466B1453F287223ADA /* SwipeRightAction.swift */,
|
||||||
7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */,
|
7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */,
|
||||||
4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */,
|
|
||||||
A1BF12A5E7C76777C4BF0F2B /* TimelineItemMenu.swift */,
|
|
||||||
0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */,
|
0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */,
|
||||||
|
3E69187DDC5E781EB96A7BED /* ItemMenu */,
|
||||||
45778D52AECD4EB99A289214 /* Polls */,
|
45778D52AECD4EB99A289214 /* Polls */,
|
||||||
4820FFB9F4FDDFD95763D498 /* ReadReceipts */,
|
4820FFB9F4FDDFD95763D498 /* ReadReceipts */,
|
||||||
1D8572B713A11CFDBF009B2F /* Replies */,
|
1D8572B713A11CFDBF009B2F /* Replies */,
|
||||||
@@ -6585,8 +6599,10 @@
|
|||||||
6B05AA5D9BBCD6D8D63B80EB /* TimelineItemAccessibilityModifier.swift in Sources */,
|
6B05AA5D9BBCD6D8D63B80EB /* TimelineItemAccessibilityModifier.swift in Sources */,
|
||||||
157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */,
|
157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */,
|
||||||
FBCCF1EA25A071324FCD8544 /* TimelineItemDebugView.swift in Sources */,
|
FBCCF1EA25A071324FCD8544 /* TimelineItemDebugView.swift in Sources */,
|
||||||
020C530986D7B97631877FEF /* TimelineItemMacContextMenu.swift in Sources */,
|
B0BA59A46ACCF0A3ECBBB7E0 /* TimelineItemMacContextMenu.swift in Sources */,
|
||||||
858C04B62166B5BAFCD20F2D /* TimelineItemMenu.swift in Sources */,
|
62833C090D599023D92A0424 /* TimelineItemMenu.swift in Sources */,
|
||||||
|
F541922A5B28C995E0BDB4E7 /* TimelineItemMenuAction.swift in Sources */,
|
||||||
|
71C532CDC9995236FC1B6EE6 /* TimelineItemMenuActionProvider.swift in Sources */,
|
||||||
1C815DD79B401DEBA2914773 /* TimelineItemMock.swift in Sources */,
|
1C815DD79B401DEBA2914773 /* TimelineItemMock.swift in Sources */,
|
||||||
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
|
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
|
||||||
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */,
|
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */,
|
||||||
|
|||||||
@@ -59,8 +59,6 @@ class RoomScreenInteractionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var canCurrentUserRedactOthers = false
|
|
||||||
private var canCurrentUserRedactSelf = false
|
|
||||||
private var resumeVoiceMessagePlaybackAfterScrubbing = false
|
private var resumeVoiceMessagePlaybackAfterScrubbing = false
|
||||||
|
|
||||||
init(roomProxy: RoomProxyProtocol,
|
init(roomProxy: RoomProxyProtocol,
|
||||||
@@ -84,17 +82,12 @@ class RoomScreenInteractionHandler {
|
|||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
self.analyticsService = analyticsService
|
self.analyticsService = analyticsService
|
||||||
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
|
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
|
||||||
|
|
||||||
// Set initial values for redacting from the macOS context menu.
|
|
||||||
Task { await updatePermissions() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Timeline Item Action Menu
|
// MARK: Timeline Item Action Menu
|
||||||
|
|
||||||
func displayTimelineItemActionMenu(for itemID: TimelineItemIdentifier) {
|
func displayTimelineItemActionMenu(for itemID: TimelineItemIdentifier) {
|
||||||
Task {
|
Task {
|
||||||
await updatePermissions()
|
|
||||||
|
|
||||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
// Don't show a menu for non-event based items.
|
// Don't show a menu for non-event based items.
|
||||||
@@ -106,84 +99,6 @@ class RoomScreenInteractionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity
|
|
||||||
func timelineItemMenuActionsForItemId(_ itemID: TimelineItemIdentifier) -> TimelineItemMenuActions? {
|
|
||||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
|
||||||
let item = timelineItem as? EventBasedTimelineItemProtocol else {
|
|
||||||
// Don't show a context menu for non-event based items.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if timelineItem is StateRoomTimelineItem {
|
|
||||||
// Don't show a context menu for state events.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var debugActions: [TimelineItemMenuAction] = []
|
|
||||||
if appSettings.viewSourceEnabled {
|
|
||||||
debugActions.append(.viewSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let encryptedItem = timelineItem as? EncryptedRoomTimelineItem {
|
|
||||||
switch encryptedItem.encryptionType {
|
|
||||||
case .megolmV1AesSha2(let sessionID, _):
|
|
||||||
debugActions.append(.retryDecryption(sessionID: sessionID))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return .init(actions: [.copyPermalink], debugActions: debugActions)
|
|
||||||
}
|
|
||||||
|
|
||||||
var actions: [TimelineItemMenuAction] = []
|
|
||||||
|
|
||||||
if item.canBeRepliedTo {
|
|
||||||
if let messageItem = item as? EventBasedMessageTimelineItemProtocol {
|
|
||||||
actions.append(.reply(isThread: messageItem.isThreaded))
|
|
||||||
} else {
|
|
||||||
actions.append(.reply(isThread: false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.isForwardable {
|
|
||||||
actions.append(.forward(itemID: itemID))
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.isEditable {
|
|
||||||
actions.append(.edit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.isCopyable {
|
|
||||||
actions.append(.copy)
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.isRemoteMessage {
|
|
||||||
actions.append(.copyPermalink)
|
|
||||||
}
|
|
||||||
|
|
||||||
if canRedactItem(item), let poll = item.pollIfAvailable, !poll.hasEnded, let eventID = itemID.eventID {
|
|
||||||
actions.append(.endPoll(pollStartID: eventID))
|
|
||||||
}
|
|
||||||
|
|
||||||
if canRedactItem(item) {
|
|
||||||
actions.append(.redact)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !item.isOutgoing {
|
|
||||||
actions.append(.report)
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.hasFailedToSend {
|
|
||||||
actions = actions.filter(\.canAppearInFailedEcho)
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.isRedacted {
|
|
||||||
actions = actions.filter(\.canAppearInRedacted)
|
|
||||||
}
|
|
||||||
|
|
||||||
return .init(actions: actions, debugActions: debugActions)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
func handleTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: TimelineItemIdentifier) {
|
||||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID),
|
||||||
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
@@ -602,24 +517,6 @@ class RoomScreenInteractionHandler {
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func updatePermissions() async {
|
|
||||||
if case let .success(value) = await roomProxy.canUserRedactOther(userID: roomProxy.ownUserID) {
|
|
||||||
canCurrentUserRedactOthers = value
|
|
||||||
} else {
|
|
||||||
canCurrentUserRedactOthers = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if case let .success(value) = await roomProxy.canUserRedactOwn(userID: roomProxy.ownUserID) {
|
|
||||||
canCurrentUserRedactSelf = value
|
|
||||||
} else {
|
|
||||||
canCurrentUserRedactSelf = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func canRedactItem(_ item: EventBasedTimelineItemProtocol) -> Bool {
|
|
||||||
item.isOutgoing ? canCurrentUserRedactSelf : canCurrentUserRedactOthers && !roomProxy.isDirect
|
|
||||||
}
|
|
||||||
|
|
||||||
private func buildReplyInfo(for item: EventBasedTimelineItemProtocol) -> ReplyInfo {
|
private func buildReplyInfo(for item: EventBasedTimelineItemProtocol) -> ReplyInfo {
|
||||||
switch item {
|
switch item {
|
||||||
case let messageItem as EventBasedMessageTimelineItemProtocol:
|
case let messageItem as EventBasedMessageTimelineItemProtocol:
|
||||||
|
|||||||
@@ -161,15 +161,15 @@ struct RoomScreenViewState: BindableState {
|
|||||||
var timelineViewState: TimelineViewState // check the doc before changing this
|
var timelineViewState: TimelineViewState // check the doc before changing this
|
||||||
|
|
||||||
var ownUserID: String
|
var ownUserID: String
|
||||||
|
var canCurrentUserRedactOthers = false
|
||||||
|
var canCurrentUserRedactSelf = false
|
||||||
|
var isViewSourceEnabled: Bool
|
||||||
|
|
||||||
var canJoinCall = false
|
var canJoinCall = false
|
||||||
var hasOngoingCall = false
|
var hasOngoingCall = false
|
||||||
|
|
||||||
var bindings: RoomScreenViewStateBindings
|
var bindings: RoomScreenViewStateBindings
|
||||||
|
|
||||||
/// A closure providing the actions to show when long pressing on an item in the timeline.
|
|
||||||
var timelineItemMenuActionProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> TimelineItemMenuActions?)?
|
|
||||||
|
|
||||||
/// A closure providing the associated audio player state for an item in the timeline.
|
/// A closure providing the associated audio player state for an item in the timeline.
|
||||||
var audioPlayerStateProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> AudioPlayerState?)?
|
var audioPlayerStateProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> AudioPlayerState?)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom,
|
isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom,
|
||||||
timelineViewState: TimelineViewState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
|
timelineViewState: TimelineViewState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
|
||||||
ownUserID: roomProxy.ownUserID,
|
ownUserID: roomProxy.ownUserID,
|
||||||
|
isViewSourceEnabled: appSettings.viewSourceEnabled,
|
||||||
hasOngoingCall: roomProxy.hasOngoingCall,
|
hasOngoingCall: roomProxy.hasOngoingCall,
|
||||||
bindings: .init(reactionsCollapsed: [:])),
|
bindings: .init(reactionsCollapsed: [:])),
|
||||||
imageProvider: mediaProvider)
|
imageProvider: mediaProvider)
|
||||||
@@ -98,13 +99,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
setupSubscriptions()
|
setupSubscriptions()
|
||||||
setupDirectRoomSubscriptionsIfNeeded()
|
setupDirectRoomSubscriptionsIfNeeded()
|
||||||
|
|
||||||
state.timelineItemMenuActionProvider = { [weak self] itemID -> TimelineItemMenuActions? in
|
// Set initial values for redacting from the macOS context menu.
|
||||||
guard let self else {
|
Task { await updatePermissions() }
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.roomScreenInteractionHandler.timelineItemMenuActionsForItemId(itemID)
|
|
||||||
}
|
|
||||||
|
|
||||||
state.audioPlayerStateProvider = { [weak self] itemID -> AudioPlayerState? in
|
state.audioPlayerStateProvider = { [weak self] itemID -> AudioPlayerState? in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@@ -351,6 +347,20 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updatePermissions() async {
|
||||||
|
if case let .success(value) = await roomProxy.canUserRedactOther(userID: roomProxy.ownUserID) {
|
||||||
|
state.canCurrentUserRedactOthers = value
|
||||||
|
} else {
|
||||||
|
state.canCurrentUserRedactOthers = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .success(value) = await roomProxy.canUserRedactOwn(userID: roomProxy.ownUserID) {
|
||||||
|
state.canCurrentUserRedactSelf = value
|
||||||
|
} else {
|
||||||
|
state.canCurrentUserRedactSelf = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func setupSubscriptions() {
|
private func setupSubscriptions() {
|
||||||
timelineController.callbacks
|
timelineController.callbacks
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
@@ -393,6 +403,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
.weakAssign(to: \.state.showReadReceipts, on: self)
|
.weakAssign(to: \.state.showReadReceipts, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
appSettings.$viewSourceEnabled
|
||||||
|
.weakAssign(to: \.state.isViewSourceEnabled, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
roomProxy.membersPublisher
|
roomProxy.membersPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] in self?.updateMembers($0) }
|
.sink { [weak self] in self?.updateMembers($0) }
|
||||||
@@ -429,7 +443,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
case .displayRoomMemberDetails(userID: let userID):
|
case .displayRoomMemberDetails(userID: let userID):
|
||||||
actionsSubject.send(.displayRoomMemberDetails(userID: userID))
|
actionsSubject.send(.displayRoomMemberDetails(userID: userID))
|
||||||
case .showActionMenu(let actionMenuInfo):
|
case .showActionMenu(let actionMenuInfo):
|
||||||
state.bindings.actionMenuInfo = actionMenuInfo
|
Task {
|
||||||
|
await self.updatePermissions()
|
||||||
|
self.state.bindings.actionMenuInfo = actionMenuInfo
|
||||||
|
}
|
||||||
case .showDebugInfo(let debugInfo):
|
case .showDebugInfo(let debugInfo):
|
||||||
state.bindings.debugInfo = debugInfo
|
state.bindings.debugInfo = debugInfo
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -21,14 +21,14 @@ import SwiftUI
|
|||||||
/// The contents of the context menu shown when right clicking an item in the timeline on a Mac
|
/// The contents of the context menu shown when right clicking an item in the timeline on a Mac
|
||||||
struct TimelineItemMacContextMenu: View {
|
struct TimelineItemMacContextMenu: View {
|
||||||
let item: RoomTimelineItemProtocol
|
let item: RoomTimelineItemProtocol
|
||||||
let actionProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> TimelineItemMenuActions?)?
|
let actionProvider: TimelineItemMenuActionProvider
|
||||||
let send: (TimelineItemMenuAction) -> Void
|
let send: (TimelineItemMenuAction) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if ProcessInfo.processInfo.isiOSAppOnMac {
|
if ProcessInfo.processInfo.isiOSAppOnMac {
|
||||||
if let menuActions = actionProvider?(item.id) {
|
if let menuActions = actionProvider.makeActions() {
|
||||||
Section {
|
Section {
|
||||||
if item.isReactable {
|
if !menuActions.reactions.isEmpty {
|
||||||
if #available(iOS 17.0, *) {
|
if #available(iOS 17.0, *) {
|
||||||
let reactions = (item as? EventBasedTimelineItemProtocol)?.properties.reactions ?? []
|
let reactions = (item as? EventBasedTimelineItemProtocol)?.properties.reactions ?? []
|
||||||
ControlGroup {
|
ControlGroup {
|
||||||
+2
-136
@@ -15,142 +15,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Compound
|
import Compound
|
||||||
import SFSafeSymbols
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TimelineItemMenuActions {
|
|
||||||
let reactions: [TimelineItemMenuReaction]
|
|
||||||
let actions: [TimelineItemMenuAction]
|
|
||||||
let debugActions: [TimelineItemMenuAction]
|
|
||||||
|
|
||||||
init?(actions: [TimelineItemMenuAction], debugActions: [TimelineItemMenuAction]) {
|
|
||||||
if actions.isEmpty, debugActions.isEmpty {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
self.actions = actions
|
|
||||||
self.debugActions = debugActions
|
|
||||||
reactions = [
|
|
||||||
.init(key: "👍️", symbol: .handThumbsup),
|
|
||||||
.init(key: "👎️", symbol: .handThumbsdown),
|
|
||||||
.init(key: "🔥", symbol: .flame),
|
|
||||||
.init(key: "❤️", symbol: .heart),
|
|
||||||
.init(key: "👏", symbol: .handsClap)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
var canReply: Bool {
|
|
||||||
for action in actions {
|
|
||||||
if case .reply = action {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimelineItemMenuReaction {
|
|
||||||
let key: String
|
|
||||||
let symbol: SFSymbol
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TimelineItemMenuAction: Identifiable, Hashable {
|
|
||||||
case copy
|
|
||||||
case edit
|
|
||||||
case copyPermalink
|
|
||||||
case redact
|
|
||||||
case reply(isThread: Bool)
|
|
||||||
case forward(itemID: TimelineItemIdentifier)
|
|
||||||
case viewSource
|
|
||||||
case retryDecryption(sessionID: String)
|
|
||||||
case report
|
|
||||||
case react
|
|
||||||
case toggleReaction(key: String)
|
|
||||||
case endPoll(pollStartID: String)
|
|
||||||
|
|
||||||
var id: Self { self }
|
|
||||||
|
|
||||||
/// Whether the item should cancel a reply/edit occurring in the composer.
|
|
||||||
var switchToDefaultComposer: Bool {
|
|
||||||
switch self {
|
|
||||||
case .reply, .edit:
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the action should be shown for an item that failed to send.
|
|
||||||
var canAppearInFailedEcho: Bool {
|
|
||||||
switch self {
|
|
||||||
case .copy, .edit, .redact, .viewSource:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the action should be shown for a redacted item.
|
|
||||||
var canAppearInRedacted: Bool {
|
|
||||||
switch self {
|
|
||||||
case .viewSource:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether or not the action is destructive.
|
|
||||||
var isDestructive: Bool {
|
|
||||||
switch self {
|
|
||||||
case .redact, .report:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The action's label.
|
|
||||||
@ViewBuilder
|
|
||||||
var label: some View {
|
|
||||||
switch self {
|
|
||||||
case .copy:
|
|
||||||
Label(L10n.actionCopy, icon: \.copy)
|
|
||||||
case .edit:
|
|
||||||
Label(L10n.actionEdit, icon: \.edit)
|
|
||||||
case .copyPermalink:
|
|
||||||
Label(L10n.actionCopyLinkToMessage, icon: \.link)
|
|
||||||
case .reply(let isThread):
|
|
||||||
Label(isThread ? L10n.actionReplyInThread : L10n.actionReply, icon: \.reply)
|
|
||||||
case .forward:
|
|
||||||
Label(L10n.actionForward, icon: \.forward)
|
|
||||||
case .redact:
|
|
||||||
Label(L10n.actionRemove, icon: \.delete)
|
|
||||||
case .viewSource:
|
|
||||||
Label(L10n.actionViewSource, icon: \.code)
|
|
||||||
case .retryDecryption:
|
|
||||||
Label(L10n.actionRetryDecryption, systemImage: "arrow.down.message")
|
|
||||||
case .report:
|
|
||||||
Label(L10n.actionReportContent, icon: \.chatProblem)
|
|
||||||
case .react:
|
|
||||||
Label(L10n.actionReact, icon: \.reactionAdd)
|
|
||||||
case .toggleReaction:
|
|
||||||
// Unused label - manually created in TimelineItemMacContextMenu.
|
|
||||||
Label(L10n.actionReact, icon: \.reactionAdd)
|
|
||||||
case .endPoll:
|
|
||||||
Label(L10n.actionEndPoll, icon: \.pollsEnd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RoomTimelineItemProtocol {
|
|
||||||
var isReactable: Bool {
|
|
||||||
guard let eventItem = self as? EventBasedTimelineItemProtocol else { return false }
|
|
||||||
return !eventItem.isRedacted && !eventItem.hasFailedToSend && !eventItem.hasFailedDecryption
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimelineItemMenu: View {
|
struct TimelineItemMenu: View {
|
||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@@ -171,7 +37,7 @@ struct TimelineItemMenu: View {
|
|||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 0.0) {
|
VStack(alignment: .leading, spacing: 0.0) {
|
||||||
if item.isReactable {
|
if !actions.reactions.isEmpty {
|
||||||
reactionsSection
|
reactionsSection
|
||||||
.padding(.top, 4.0)
|
.padding(.top, 4.0)
|
||||||
.padding(.bottom, 8.0)
|
.padding(.bottom, 8.0)
|
||||||
@@ -317,7 +183,7 @@ struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
static var testView: some View {
|
static var testView: some View {
|
||||||
if let item = RoomTimelineItemFixtures.singleMessageChunk.first as? EventBasedTimelineItemProtocol,
|
if let item = RoomTimelineItemFixtures.singleMessageChunk.first as? EventBasedTimelineItemProtocol,
|
||||||
let actions = TimelineItemMenuActions(actions: [.copy, .edit, .reply(isThread: false), .redact], debugActions: [.viewSource]) {
|
let actions = TimelineItemMenuActions(isReactable: true, actions: [.copy, .edit, .reply(isThread: false), .redact], debugActions: [.viewSource]) {
|
||||||
TimelineItemMenu(item: item, actions: actions)
|
TimelineItemMenu(item: item, actions: actions)
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2024 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 SFSafeSymbols
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct TimelineItemMenuActions {
|
||||||
|
let reactions: [TimelineItemMenuReaction]
|
||||||
|
let actions: [TimelineItemMenuAction]
|
||||||
|
let debugActions: [TimelineItemMenuAction]
|
||||||
|
|
||||||
|
init?(isReactable: Bool, actions: [TimelineItemMenuAction], debugActions: [TimelineItemMenuAction]) {
|
||||||
|
if !isReactable, actions.isEmpty, debugActions.isEmpty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.actions = actions
|
||||||
|
self.debugActions = debugActions
|
||||||
|
reactions = if isReactable {
|
||||||
|
[
|
||||||
|
.init(key: "👍️", symbol: .handThumbsup),
|
||||||
|
.init(key: "👎️", symbol: .handThumbsdown),
|
||||||
|
.init(key: "🔥", symbol: .flame),
|
||||||
|
.init(key: "❤️", symbol: .heart),
|
||||||
|
.init(key: "👏", symbol: .handsClap)
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimelineItemMenuReaction {
|
||||||
|
let key: String
|
||||||
|
let symbol: SFSymbol
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||||
|
case copy
|
||||||
|
case edit
|
||||||
|
case copyPermalink
|
||||||
|
case redact
|
||||||
|
case reply(isThread: Bool)
|
||||||
|
case forward(itemID: TimelineItemIdentifier)
|
||||||
|
case viewSource
|
||||||
|
case retryDecryption(sessionID: String)
|
||||||
|
case report
|
||||||
|
case react
|
||||||
|
case toggleReaction(key: String)
|
||||||
|
case endPoll(pollStartID: String)
|
||||||
|
|
||||||
|
var id: Self { self }
|
||||||
|
|
||||||
|
/// Whether the item should cancel a reply/edit occurring in the composer.
|
||||||
|
var switchToDefaultComposer: Bool {
|
||||||
|
switch self {
|
||||||
|
case .reply, .edit:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the action should be shown for an item that failed to send.
|
||||||
|
var canAppearInFailedEcho: Bool {
|
||||||
|
switch self {
|
||||||
|
case .copy, .edit, .redact, .viewSource:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the action should be shown for a redacted item.
|
||||||
|
var canAppearInRedacted: Bool {
|
||||||
|
switch self {
|
||||||
|
case .viewSource:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether or not the action is destructive.
|
||||||
|
var isDestructive: Bool {
|
||||||
|
switch self {
|
||||||
|
case .redact, .report:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The action's label.
|
||||||
|
@ViewBuilder
|
||||||
|
var label: some View {
|
||||||
|
switch self {
|
||||||
|
case .copy:
|
||||||
|
Label(L10n.actionCopy, icon: \.copy)
|
||||||
|
case .edit:
|
||||||
|
Label(L10n.actionEdit, icon: \.edit)
|
||||||
|
case .copyPermalink:
|
||||||
|
Label(L10n.actionCopyLinkToMessage, icon: \.link)
|
||||||
|
case .reply(let isThread):
|
||||||
|
Label(isThread ? L10n.actionReplyInThread : L10n.actionReply, icon: \.reply)
|
||||||
|
case .forward:
|
||||||
|
Label(L10n.actionForward, icon: \.forward)
|
||||||
|
case .redact:
|
||||||
|
Label(L10n.actionRemove, icon: \.delete)
|
||||||
|
case .viewSource:
|
||||||
|
Label(L10n.actionViewSource, icon: \.code)
|
||||||
|
case .retryDecryption:
|
||||||
|
Label(L10n.actionRetryDecryption, systemImage: "arrow.down.message")
|
||||||
|
case .report:
|
||||||
|
Label(L10n.actionReportContent, icon: \.chatProblem)
|
||||||
|
case .react:
|
||||||
|
Label(L10n.actionReact, icon: \.reactionAdd)
|
||||||
|
case .toggleReaction:
|
||||||
|
// Unused label - manually created in TimelineItemMacContextMenu.
|
||||||
|
Label(L10n.actionReact, icon: \.reactionAdd)
|
||||||
|
case .endPoll:
|
||||||
|
Label(L10n.actionEndPoll, icon: \.pollsEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2024 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 TimelineItemMenuActionProvider {
|
||||||
|
let timelineItem: RoomTimelineItemProtocol
|
||||||
|
let canCurrentUserRedactSelf: Bool
|
||||||
|
let canCurrentUserRedactOthers: Bool
|
||||||
|
let isDM: Bool
|
||||||
|
let isViewSourceEnabled: Bool
|
||||||
|
|
||||||
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
|
func makeActions() -> TimelineItemMenuActions? {
|
||||||
|
guard let item = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
|
// Don't show a context menu for non-event based items.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if timelineItem is StateRoomTimelineItem {
|
||||||
|
// Don't show a context menu for state events.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var debugActions: [TimelineItemMenuAction] = []
|
||||||
|
if isViewSourceEnabled {
|
||||||
|
debugActions.append(.viewSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let encryptedItem = timelineItem as? EncryptedRoomTimelineItem {
|
||||||
|
switch encryptedItem.encryptionType {
|
||||||
|
case .megolmV1AesSha2(let sessionID, _):
|
||||||
|
debugActions.append(.retryDecryption(sessionID: sessionID))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return .init(isReactable: false, actions: [.copyPermalink], debugActions: debugActions)
|
||||||
|
}
|
||||||
|
|
||||||
|
var actions: [TimelineItemMenuAction] = []
|
||||||
|
|
||||||
|
if item.canBeRepliedTo {
|
||||||
|
if let messageItem = item as? EventBasedMessageTimelineItemProtocol {
|
||||||
|
actions.append(.reply(isThread: messageItem.isThreaded))
|
||||||
|
} else {
|
||||||
|
actions.append(.reply(isThread: false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.isForwardable {
|
||||||
|
actions.append(.forward(itemID: item.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.isEditable {
|
||||||
|
actions.append(.edit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.isCopyable {
|
||||||
|
actions.append(.copy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.isRemoteMessage {
|
||||||
|
actions.append(.copyPermalink)
|
||||||
|
}
|
||||||
|
|
||||||
|
if canRedactItem(item), let poll = item.pollIfAvailable, !poll.hasEnded, let eventID = item.id.eventID {
|
||||||
|
actions.append(.endPoll(pollStartID: eventID))
|
||||||
|
}
|
||||||
|
|
||||||
|
if canRedactItem(item) {
|
||||||
|
actions.append(.redact)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !item.isOutgoing {
|
||||||
|
actions.append(.report)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.hasFailedToSend {
|
||||||
|
actions = actions.filter(\.canAppearInFailedEcho)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.isRedacted {
|
||||||
|
actions = actions.filter(\.canAppearInRedacted)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .init(isReactable: item.isReactable, actions: actions, debugActions: debugActions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func canRedactItem(_ item: EventBasedTimelineItemProtocol) -> Bool {
|
||||||
|
item.isOutgoing ? canCurrentUserRedactSelf : canCurrentUserRedactOthers && !isDM
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,7 +57,12 @@ struct RoomScreen: View {
|
|||||||
.alert(item: $context.alertInfo)
|
.alert(item: $context.alertInfo)
|
||||||
.sheet(item: $context.debugInfo) { TimelineItemDebugView(info: $0) }
|
.sheet(item: $context.debugInfo) { TimelineItemDebugView(info: $0) }
|
||||||
.sheet(item: $context.actionMenuInfo) { info in
|
.sheet(item: $context.actionMenuInfo) { info in
|
||||||
context.viewState.timelineItemMenuActionProvider?(info.item.id).map { actions in
|
let actions = TimelineItemMenuActionProvider(timelineItem: info.item,
|
||||||
|
canCurrentUserRedactSelf: context.viewState.canCurrentUserRedactSelf,
|
||||||
|
canCurrentUserRedactOthers: context.viewState.canCurrentUserRedactOthers,
|
||||||
|
isDM: context.viewState.isEncryptedOneToOneRoom,
|
||||||
|
isViewSourceEnabled: context.viewState.isViewSourceEnabled).makeActions()
|
||||||
|
if let actions {
|
||||||
TimelineItemMenu(item: info.item, actions: actions)
|
TimelineItemMenu(item: info.item, actions: actions)
|
||||||
.environmentObject(context)
|
.environmentObject(context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,14 +137,18 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
.swipeRightAction {
|
.swipeRightAction {
|
||||||
SwipeToReplyView(timelineItem: timelineItem)
|
SwipeToReplyView(timelineItem: timelineItem)
|
||||||
} shouldStartAction: {
|
} shouldStartAction: {
|
||||||
context.viewState.timelineItemMenuActionProvider?(timelineItem.id)?.canReply ?? false
|
timelineItem.canBeRepliedTo
|
||||||
} action: {
|
} action: {
|
||||||
let isThread = (timelineItem as? EventBasedMessageTimelineItemProtocol)?.isThreaded ?? false
|
let isThread = (timelineItem as? EventBasedMessageTimelineItemProtocol)?.isThreaded ?? false
|
||||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: .reply(isThread: isThread)))
|
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: .reply(isThread: isThread)))
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
TimelineItemMacContextMenu(item: timelineItem,
|
let provider = TimelineItemMenuActionProvider(timelineItem: timelineItem,
|
||||||
actionProvider: context.viewState.timelineItemMenuActionProvider) { action in
|
canCurrentUserRedactSelf: context.viewState.canCurrentUserRedactSelf,
|
||||||
|
canCurrentUserRedactOthers: context.viewState.canCurrentUserRedactOthers,
|
||||||
|
isDM: context.viewState.isEncryptedOneToOneRoom,
|
||||||
|
isViewSourceEnabled: context.viewState.isViewSourceEnabled)
|
||||||
|
TimelineItemMacContextMenu(item: timelineItem, actionProvider: provider) { action in
|
||||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: action))
|
context.send(viewAction: .handleTimelineItemMenuAction(itemID: timelineItem.id, action: action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,3 +20,10 @@ import UIKit
|
|||||||
protocol RoomTimelineItemProtocol {
|
protocol RoomTimelineItemProtocol {
|
||||||
var id: TimelineItemIdentifier { get }
|
var id: TimelineItemIdentifier { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension RoomTimelineItemProtocol {
|
||||||
|
var isReactable: Bool {
|
||||||
|
guard let eventItem = self as? EventBasedTimelineItemProtocol else { return false }
|
||||||
|
return !eventItem.isRedacted && !eventItem.hasFailedToSend && !eventItem.hasFailedDecryption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
||||||
await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID)?.content().asMessage()?.content()
|
await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content().asMessage()?.content()
|
||||||
}
|
}
|
||||||
|
|
||||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
|
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
|
||||||
@@ -158,7 +158,16 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
|
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
|
||||||
MXLog.info("Editing timeline item: \(timelineItemID)")
|
MXLog.info("Editing timeline item: \(timelineItemID)")
|
||||||
|
|
||||||
guard let timelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID) else {
|
let timelineItem: EventTimelineItem? = if !timelineItemID.timelineID.isEmpty,
|
||||||
|
let timelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID) {
|
||||||
|
timelineItem
|
||||||
|
} else if let eventID = timelineItemID.eventID {
|
||||||
|
nil // We need to edit by event ID which was removed.
|
||||||
|
} else {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let timelineItem else {
|
||||||
MXLog.error("Unknown timeline item: \(timelineItemID)")
|
MXLog.error("Unknown timeline item: \(timelineItemID)")
|
||||||
return .failure(.failedEditing)
|
return .failure(.failedEditing)
|
||||||
}
|
}
|
||||||
@@ -184,7 +193,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
func redact(_ timelineItemID: TimelineItemIdentifier, reason: String?) async -> Result<Void, TimelineProxyError> {
|
func redact(_ timelineItemID: TimelineItemIdentifier, reason: String?) async -> Result<Void, TimelineProxyError> {
|
||||||
MXLog.info("Redacting timeline item: \(timelineItemID)")
|
MXLog.info("Redacting timeline item: \(timelineItemID)")
|
||||||
|
|
||||||
guard let eventTimelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID) else {
|
guard let eventTimelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID) else {
|
||||||
MXLog.error("Unknown timeline item: \(timelineItemID)")
|
MXLog.error("Unknown timeline item: \(timelineItemID)")
|
||||||
return .failure(.failedRedacting)
|
return .failure(.failedRedacting)
|
||||||
}
|
}
|
||||||
@@ -600,18 +609,15 @@ private extension MatrixRustSDK.PollKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Array where Element == TimelineItemProxy {
|
extension Array where Element == TimelineItemProxy {
|
||||||
func firstEventTimelineItemUsingID(_ id: TimelineItemIdentifier) -> EventTimelineItem? {
|
func firstEventTimelineItemUsingStableID(_ id: TimelineItemIdentifier) -> EventTimelineItem? {
|
||||||
var eventTimelineItemProxy: EventTimelineItemProxy?
|
|
||||||
|
|
||||||
for item in self {
|
for item in self {
|
||||||
if case let .event(eventTimelineItem) = item {
|
if case let .event(eventTimelineItem) = item {
|
||||||
if eventTimelineItem.id == id {
|
if eventTimelineItem.id.timelineID == id.timelineID {
|
||||||
eventTimelineItemProxy = eventTimelineItem
|
return eventTimelineItem.item
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return eventTimelineItemProxy?.item
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user