mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Media gallery - part 1(#3588)
* Introduce a `MediaEventsTimelineFlowCoordinator` * Update SDK API and architecture * Add a feature flag, add translations * Move the media events timeline presentation under the room flow coordinator state machine * Rename `TimelineViewState.timelineViewState` of type `TimelineState` to `timelineState` * Enabled SwiftLint's `trailing_closure` rule and fix the warnings.
This commit is contained in:
parent
a9e4837b62
commit
caaa89af62
@ -9,6 +9,7 @@ opt_in_rules:
|
|||||||
- private_action
|
- private_action
|
||||||
- explicit_init
|
- explicit_init
|
||||||
- shorthand_optional_binding
|
- shorthand_optional_binding
|
||||||
|
- trailing_closure
|
||||||
|
|
||||||
included:
|
included:
|
||||||
- ElementX
|
- ElementX
|
||||||
|
@ -637,6 +637,7 @@
|
|||||||
847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1D4A6D451F43A03CACD01D /* PINTextField.swift */; };
|
847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1D4A6D451F43A03CACD01D /* PINTextField.swift */; };
|
||||||
84C631E734FD2555B39B681C /* RoomRolesAndPermissionsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48FEFF746DB341CDB18D7AAA /* RoomRolesAndPermissionsScreenViewModelTests.swift */; };
|
84C631E734FD2555B39B681C /* RoomRolesAndPermissionsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48FEFF746DB341CDB18D7AAA /* RoomRolesAndPermissionsScreenViewModelTests.swift */; };
|
||||||
84CAE3E96D93194DA06B9194 /* CallScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD9AD6AE5FC868962F090740 /* CallScreenViewModelProtocol.swift */; };
|
84CAE3E96D93194DA06B9194 /* CallScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD9AD6AE5FC868962F090740 /* CallScreenViewModelProtocol.swift */; };
|
||||||
|
84E514915DF0C168B08A3A0A /* MediaEventsTimelineFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2178B951602AA921A5FD9DC8 /* MediaEventsTimelineFlowCoordinator.swift */; };
|
||||||
84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; };
|
84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; };
|
||||||
8544657DEEE717ED2E22E382 /* RoomNotificationSettingsProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D1BAA90F3A073D91B4F16B /* RoomNotificationSettingsProxyMock.swift */; };
|
8544657DEEE717ED2E22E382 /* RoomNotificationSettingsProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D1BAA90F3A073D91B4F16B /* RoomNotificationSettingsProxyMock.swift */; };
|
||||||
854E82E064BA53CD0BC45600 /* LocationRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6613DE16AD26B3A74DA1F5 /* LocationRoomTimelineItemContent.swift */; };
|
854E82E064BA53CD0BC45600 /* LocationRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6613DE16AD26B3A74DA1F5 /* LocationRoomTimelineItemContent.swift */; };
|
||||||
@ -885,6 +886,7 @@
|
|||||||
B796A25F282C0A340D1B9C12 /* ImageRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B5EDCD05D50BA9B815C66C /* ImageRoomTimelineItemContent.swift */; };
|
B796A25F282C0A340D1B9C12 /* ImageRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B5EDCD05D50BA9B815C66C /* ImageRoomTimelineItemContent.swift */; };
|
||||||
B79E8AB83EBBDCD476D0362F /* PollFormScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D622EC7898469BB1D0881CDD /* PollFormScreen.swift */; };
|
B79E8AB83EBBDCD476D0362F /* PollFormScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D622EC7898469BB1D0881CDD /* PollFormScreen.swift */; };
|
||||||
B7C9E07F4F9CCC8DD7156A20 /* CallScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28146817C61423CACCF942F5 /* CallScreenModels.swift */; };
|
B7C9E07F4F9CCC8DD7156A20 /* CallScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28146817C61423CACCF942F5 /* CallScreenModels.swift */; };
|
||||||
|
B7F58D6903F9D509EDAB9E4F /* MediaEventsTimelineScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7033218DA395B003F7AB29A2 /* MediaEventsTimelineScreenModels.swift */; };
|
||||||
B818580464CFB5400A3EF6AE /* TimelineModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029D5701F80A9AF7167BB4D0 /* TimelineModels.swift */; };
|
B818580464CFB5400A3EF6AE /* TimelineModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029D5701F80A9AF7167BB4D0 /* TimelineModels.swift */; };
|
||||||
B855AF29D7D8FC8DAAA73D4A /* test_voice_message.m4a in Resources */ = {isa = PBXBuildFile; fileRef = DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */; };
|
B855AF29D7D8FC8DAAA73D4A /* test_voice_message.m4a in Resources */ = {isa = PBXBuildFile; fileRef = DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */; };
|
||||||
B879446FD8E65A711EF8F9F7 /* AdvancedSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */; };
|
B879446FD8E65A711EF8F9F7 /* AdvancedSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */; };
|
||||||
@ -915,6 +917,7 @@
|
|||||||
BDED6DA7AD1E76018C424143 /* LegalInformationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C34667458773B02AB5FB0B2 /* LegalInformationScreenViewModel.swift */; };
|
BDED6DA7AD1E76018C424143 /* LegalInformationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C34667458773B02AB5FB0B2 /* LegalInformationScreenViewModel.swift */; };
|
||||||
BE8E5985771DF9137C6CE89A /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077B01C13BBA2996272C5FB5 /* ProcessInfo.swift */; };
|
BE8E5985771DF9137C6CE89A /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077B01C13BBA2996272C5FB5 /* ProcessInfo.swift */; };
|
||||||
BEA646DF302711A753F0D420 /* MapTilerStyleBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225EFCA26877E75CDFE7F48D /* MapTilerStyleBuilderProtocol.swift */; };
|
BEA646DF302711A753F0D420 /* MapTilerStyleBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225EFCA26877E75CDFE7F48D /* MapTilerStyleBuilderProtocol.swift */; };
|
||||||
|
BEC6DFEA506085D3027E353C /* MediaEventsTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002399C6CB875C4EBB01CBC0 /* MediaEventsTimelineScreen.swift */; };
|
||||||
BFEB24336DFD5F196E6F3456 /* IntentionalMentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF5CBAF69BDF5DF31C661E1 /* IntentionalMentions.swift */; };
|
BFEB24336DFD5F196E6F3456 /* IntentionalMentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF5CBAF69BDF5DF31C661E1 /* IntentionalMentions.swift */; };
|
||||||
C0090506A52A1991BAF4BA68 /* NotificationSettingsChatType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07579F9C29001E40715F3014 /* NotificationSettingsChatType.swift */; };
|
C0090506A52A1991BAF4BA68 /* NotificationSettingsChatType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07579F9C29001E40715F3014 /* NotificationSettingsChatType.swift */; };
|
||||||
C022284E2774A5E1EF683B4D /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; };
|
C022284E2774A5E1EF683B4D /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; };
|
||||||
@ -922,6 +925,7 @@
|
|||||||
C08AAE7563E0722C9383F51C /* RoomMembersListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B8E176484A89BAC389D4076 /* RoomMembersListScreen.swift */; };
|
C08AAE7563E0722C9383F51C /* RoomMembersListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B8E176484A89BAC389D4076 /* RoomMembersListScreen.swift */; };
|
||||||
C0B97FFEC0083F3A36609E61 /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A243A6E6207297123E60DE48 /* TimelineItemMacContextMenu.swift */; };
|
C0B97FFEC0083F3A36609E61 /* TimelineItemMacContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A243A6E6207297123E60DE48 /* TimelineItemMacContextMenu.swift */; };
|
||||||
C11939FDC40716C4387275A4 /* NotificationSettingsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */; };
|
C11939FDC40716C4387275A4 /* NotificationSettingsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */; };
|
||||||
|
C11D4A49DC29D89CE2BB31B8 /* MediaEventsTimelineScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976ED77B772F50C4BAD757E7 /* MediaEventsTimelineScreenViewModel.swift */; };
|
||||||
C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */; };
|
C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */; };
|
||||||
C1429699A6A5BB09A25775C1 /* AudioPlayerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */; };
|
C1429699A6A5BB09A25775C1 /* AudioPlayerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */; };
|
||||||
C1910A16BDF131FECA77BE22 /* EmojiPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */; };
|
C1910A16BDF131FECA77BE22 /* EmojiPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */; };
|
||||||
@ -960,6 +964,7 @@
|
|||||||
C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; };
|
C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; };
|
||||||
C8C7AF33AADF88B306CD2695 /* QRCodeLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4427AF4B7FB7EF3E3D424C7 /* QRCodeLoginService.swift */; };
|
C8C7AF33AADF88B306CD2695 /* QRCodeLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4427AF4B7FB7EF3E3D424C7 /* QRCodeLoginService.swift */; };
|
||||||
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; };
|
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; };
|
||||||
|
C8E1E4E06B7C7A3A8246FC9B /* MediaEventsTimelineScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8512B82404B1751D0BCC82D2 /* MediaEventsTimelineScreenCoordinator.swift */; };
|
||||||
C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */; };
|
C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */; };
|
||||||
C969A62F3D9F14318481A33B /* KnockedRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858DA81F2ACF484B7CAD6AE4 /* KnockedRoomProxy.swift */; };
|
C969A62F3D9F14318481A33B /* KnockedRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858DA81F2ACF484B7CAD6AE4 /* KnockedRoomProxy.swift */; };
|
||||||
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; };
|
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; };
|
||||||
@ -1203,6 +1208,7 @@
|
|||||||
FD29471C72872F8B7580E3E1 /* KeychainControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */; };
|
FD29471C72872F8B7580E3E1 /* KeychainControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */; };
|
||||||
FD4C21F8DA1E273DE94FCD1A /* NotificationItemProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B927CF5EF7FCCDA5EDC474B /* NotificationItemProxyProtocol.swift */; };
|
FD4C21F8DA1E273DE94FCD1A /* NotificationItemProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B927CF5EF7FCCDA5EDC474B /* NotificationItemProxyProtocol.swift */; };
|
||||||
FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */; };
|
FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */; };
|
||||||
|
FD9777315A5D9CDC47458AD1 /* MediaEventsTimelineScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A175D0FDEDBFA44C47FE13AE /* MediaEventsTimelineScreenViewModelProtocol.swift */; };
|
||||||
FDC67E8C0EDCB00ABC66C859 /* landscape_test_video.mov in Resources */ = {isa = PBXBuildFile; fileRef = 78BBDF7A05CF53B5CDC13682 /* landscape_test_video.mov */; };
|
FDC67E8C0EDCB00ABC66C859 /* landscape_test_video.mov in Resources */ = {isa = PBXBuildFile; fileRef = 78BBDF7A05CF53B5CDC13682 /* landscape_test_video.mov */; };
|
||||||
FDD5B4B616D9FF4DE3E9A418 /* QRCodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92DB574F954CC2B40F7BE892 /* QRCodeScannerView.swift */; };
|
FDD5B4B616D9FF4DE3E9A418 /* QRCodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92DB574F954CC2B40F7BE892 /* QRCodeScannerView.swift */; };
|
||||||
FDE47D4686BA0F86BB584633 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = CAA3B9DF998B397C9EE64E8B /* Collections */; };
|
FDE47D4686BA0F86BB584633 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = CAA3B9DF998B397C9EE64E8B /* Collections */; };
|
||||||
@ -1287,6 +1293,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
002399C6CB875C4EBB01CBC0 /* MediaEventsTimelineScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreen.swift; sourceTree = "<group>"; };
|
||||||
00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = "<group>"; };
|
00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = "<group>"; };
|
||||||
00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreen.swift; sourceTree = "<group>"; };
|
00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreen.swift; sourceTree = "<group>"; };
|
||||||
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
@ -1445,6 +1452,7 @@
|
|||||||
20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
2141693488CE5446BB391964 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
|
2141693488CE5446BB391964 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
|
||||||
216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||||
|
2178B951602AA921A5FD9DC8 /* MediaEventsTimelineFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||||
218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||||
21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
21DD8599815136EFF5B73F38 /* UserFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFlowTests.swift; sourceTree = "<group>"; };
|
21DD8599815136EFF5B73F38 /* UserFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFlowTests.swift; sourceTree = "<group>"; };
|
||||||
@ -1802,6 +1810,7 @@
|
|||||||
6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsBannerStateTests.swift; sourceTree = "<group>"; };
|
6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsBannerStateTests.swift; sourceTree = "<group>"; };
|
||||||
6FC5015B9634698BDB8701AF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
6FC5015B9634698BDB8701AF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallServiceProtocol.swift; sourceTree = "<group>"; };
|
6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallServiceProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
7033218DA395B003F7AB29A2 /* MediaEventsTimelineScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreenModels.swift; sourceTree = "<group>"; };
|
||||||
7061BE2C0BF427C38AEDEF5E /* SecureBackupRecoveryKeyScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenViewModel.swift; sourceTree = "<group>"; };
|
7061BE2C0BF427C38AEDEF5E /* SecureBackupRecoveryKeyScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreen.swift; sourceTree = "<group>"; };
|
70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreen.swift; sourceTree = "<group>"; };
|
||||||
713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
@ -1892,6 +1901,7 @@
|
|||||||
84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModel.swift; sourceTree = "<group>"; };
|
84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
84AF32E4136FD6F159D86C2C /* RoomDirectorySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchView.swift; sourceTree = "<group>"; };
|
84AF32E4136FD6F159D86C2C /* RoomDirectorySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchView.swift; sourceTree = "<group>"; };
|
||||||
84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunnerTests.swift; sourceTree = "<group>"; };
|
84B7A28A6606D58D1E38C55A /* ExpiringTaskRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
8512B82404B1751D0BCC82D2 /* MediaEventsTimelineScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
851B95BB98649B8E773D6790 /* AppLockService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockService.swift; sourceTree = "<group>"; };
|
851B95BB98649B8E773D6790 /* AppLockService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockService.swift; sourceTree = "<group>"; };
|
||||||
8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
@ -1975,6 +1985,7 @@
|
|||||||
96CE9D6642DD487D8CC90C9C /* landscape_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = landscape_test_image.jpg; sourceTree = "<group>"; };
|
96CE9D6642DD487D8CC90C9C /* landscape_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = landscape_test_image.jpg; sourceTree = "<group>"; };
|
||||||
97287090CA64DAA95386ECED /* ResolveVerifiedUserSendFailureScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolveVerifiedUserSendFailureScreen.swift; sourceTree = "<group>"; };
|
97287090CA64DAA95386ECED /* ResolveVerifiedUserSendFailureScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolveVerifiedUserSendFailureScreen.swift; sourceTree = "<group>"; };
|
||||||
974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPermissions.swift; sourceTree = "<group>"; };
|
974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPermissions.swift; sourceTree = "<group>"; };
|
||||||
|
976ED77B772F50C4BAD757E7 /* MediaEventsTimelineScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
9780389F8A53E4D26E23DD03 /* LoginScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
9780389F8A53E4D26E23DD03 /* LoginScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
97B2ACA28A854E41AE3AC9AD /* TimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModel.swift; sourceTree = "<group>"; };
|
97B2ACA28A854E41AE3AC9AD /* TimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModel.swift; sourceTree = "<group>"; };
|
||||||
97C8E13A1FBA717B0C277ECC /* ProgressCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressCursorModifier.swift; sourceTree = "<group>"; };
|
97C8E13A1FBA717B0C277ECC /* ProgressCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressCursorModifier.swift; sourceTree = "<group>"; };
|
||||||
@ -2021,6 +2032,7 @@
|
|||||||
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>"; };
|
||||||
|
A175D0FDEDBFA44C47FE13AE /* MediaEventsTimelineScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreenViewModelProtocol.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>"; };
|
||||||
A243A6E6207297123E60DE48 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = "<group>"; };
|
A243A6E6207297123E60DE48 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = "<group>"; };
|
||||||
@ -2908,6 +2920,18 @@
|
|||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
26397A1EDB867FD573821532 /* MediaEventsTimelineScreen */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
8512B82404B1751D0BCC82D2 /* MediaEventsTimelineScreenCoordinator.swift */,
|
||||||
|
7033218DA395B003F7AB29A2 /* MediaEventsTimelineScreenModels.swift */,
|
||||||
|
976ED77B772F50C4BAD757E7 /* MediaEventsTimelineScreenViewModel.swift */,
|
||||||
|
A175D0FDEDBFA44C47FE13AE /* MediaEventsTimelineScreenViewModelProtocol.swift */,
|
||||||
|
DB180A1068D7B85489E13E3F /* View */,
|
||||||
|
);
|
||||||
|
path = MediaEventsTimelineScreen;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
26C16326BCCCED74A85A0F48 /* View */ = {
|
26C16326BCCCED74A85A0F48 /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -3704,6 +3728,7 @@
|
|||||||
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */,
|
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */,
|
||||||
A07B011547B201A836C03052 /* EncryptionResetFlowCoordinator.swift */,
|
A07B011547B201A836C03052 /* EncryptionResetFlowCoordinator.swift */,
|
||||||
ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */,
|
ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */,
|
||||||
|
2178B951602AA921A5FD9DC8 /* MediaEventsTimelineFlowCoordinator.swift */,
|
||||||
C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */,
|
C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */,
|
||||||
A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */,
|
A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */,
|
||||||
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
|
9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */,
|
||||||
@ -5371,6 +5396,14 @@
|
|||||||
path = RoomChangePermissionsScreen;
|
path = RoomChangePermissionsScreen;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
DB180A1068D7B85489E13E3F /* View */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
002399C6CB875C4EBB01CBC0 /* MediaEventsTimelineScreen.swift */,
|
||||||
|
);
|
||||||
|
path = View;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
DD96B3F20F354494DECBC4F7 /* View */ = {
|
DD96B3F20F354494DECBC4F7 /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -5469,6 +5502,7 @@
|
|||||||
BF0415BE807CA2BCFC210008 /* KnockRequestsListScreen */,
|
BF0415BE807CA2BCFC210008 /* KnockRequestsListScreen */,
|
||||||
948DD12A5533BE1BC260E437 /* LocationSharing */,
|
948DD12A5533BE1BC260E437 /* LocationSharing */,
|
||||||
73E032ADD008D63812791D97 /* LogViewerScreen */,
|
73E032ADD008D63812791D97 /* LogViewerScreen */,
|
||||||
|
26397A1EDB867FD573821532 /* MediaEventsTimelineScreen */,
|
||||||
87E2774157D9C4894BCFF3F8 /* MediaPickerScreen */,
|
87E2774157D9C4894BCFF3F8 /* MediaPickerScreen */,
|
||||||
23605DD08620BE6558242469 /* MediaUploadPreviewScreen */,
|
23605DD08620BE6558242469 /* MediaUploadPreviewScreen */,
|
||||||
3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */,
|
3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */,
|
||||||
@ -6933,6 +6967,12 @@
|
|||||||
BEA646DF302711A753F0D420 /* MapTilerStyleBuilderProtocol.swift in Sources */,
|
BEA646DF302711A753F0D420 /* MapTilerStyleBuilderProtocol.swift in Sources */,
|
||||||
67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */,
|
67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */,
|
||||||
8658F5034EAD7357CE7F9AC7 /* MatrixUserShareLink.swift in Sources */,
|
8658F5034EAD7357CE7F9AC7 /* MatrixUserShareLink.swift in Sources */,
|
||||||
|
84E514915DF0C168B08A3A0A /* MediaEventsTimelineFlowCoordinator.swift in Sources */,
|
||||||
|
BEC6DFEA506085D3027E353C /* MediaEventsTimelineScreen.swift in Sources */,
|
||||||
|
C8E1E4E06B7C7A3A8246FC9B /* MediaEventsTimelineScreenCoordinator.swift in Sources */,
|
||||||
|
B7F58D6903F9D509EDAB9E4F /* MediaEventsTimelineScreenModels.swift in Sources */,
|
||||||
|
C11D4A49DC29D89CE2BB31B8 /* MediaEventsTimelineScreenViewModel.swift in Sources */,
|
||||||
|
FD9777315A5D9CDC47458AD1 /* MediaEventsTimelineScreenViewModelProtocol.swift in Sources */,
|
||||||
BCC864190651B3A3CF51E4DF /* MediaFileHandleProxy.swift in Sources */,
|
BCC864190651B3A3CF51E4DF /* MediaFileHandleProxy.swift in Sources */,
|
||||||
208C19811613F9A10F8A7B75 /* MediaLoader.swift in Sources */,
|
208C19811613F9A10F8A7B75 /* MediaLoader.swift in Sources */,
|
||||||
A2434D4DFB49A68E5CD0F53C /* MediaLoaderProtocol.swift in Sources */,
|
A2434D4DFB49A68E5CD0F53C /* MediaLoaderProtocol.swift in Sources */,
|
||||||
|
@ -1048,7 +1048,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
.actionsPublisher
|
.actionsPublisher
|
||||||
.filter(\.isSyncUpdate)
|
.filter(\.isSyncUpdate)
|
||||||
.collect(.byTimeOrCount(DispatchQueue.main, .seconds(10), 10))
|
.collect(.byTimeOrCount(DispatchQueue.main, .seconds(10), 10))
|
||||||
.sink(receiveValue: { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
MXLog.info("Background app refresh finished")
|
MXLog.info("Background app refresh finished")
|
||||||
backgroundRefreshSyncObserver?.cancel()
|
backgroundRefreshSyncObserver?.cancel()
|
||||||
@ -1059,6 +1059,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
MXLog.info("Marking Background app refresh task as complete.")
|
MXLog.info("Marking Background app refresh task as complete.")
|
||||||
task.setTaskCompleted(success: true)
|
task.setTaskCompleted(success: true)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ final class AppSettings {
|
|||||||
case enableOnlySignedDeviceIsolationMode
|
case enableOnlySignedDeviceIsolationMode
|
||||||
case knockingEnabled
|
case knockingEnabled
|
||||||
case createMediaCaptionsEnabled
|
case createMediaCaptionsEnabled
|
||||||
|
case mediaBrowserEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
||||||
@ -288,6 +289,9 @@ final class AppSettings {
|
|||||||
@UserPreference(key: UserDefaultsKeys.createMediaCaptionsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.createMediaCaptionsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||||
var createMediaCaptionsEnabled
|
var createMediaCaptionsEnabled
|
||||||
|
|
||||||
|
@UserPreference(key: UserDefaultsKeys.mediaBrowserEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||||
|
var mediaBrowserEnabled
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MARK: - Shared
|
// MARK: - Shared
|
||||||
|
@ -42,11 +42,11 @@ struct Application: App {
|
|||||||
openURLInSystemBrowser($0)
|
openURLInSystemBrowser($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onContinueUserActivity("INStartVideoCallIntent", perform: { userActivity in
|
.onContinueUserActivity("INStartVideoCallIntent") { userActivity in
|
||||||
// `INStartVideoCallIntent` is to be replaced with `INStartCallIntent`
|
// `INStartVideoCallIntent` is to be replaced with `INStartCallIntent`
|
||||||
// but calls from Recents still send it ¯\_(ツ)_/¯
|
// but calls from Recents still send it ¯\_(ツ)_/¯
|
||||||
appCoordinator.handleUserActivity(userActivity)
|
appCoordinator.handleUserActivity(userActivity)
|
||||||
})
|
}
|
||||||
.task {
|
.task {
|
||||||
appCoordinator.start()
|
appCoordinator.start()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
// Please see LICENSE in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum MediaEventsTimelineFlowCoordinatorAction {
|
||||||
|
case finished
|
||||||
|
}
|
||||||
|
|
||||||
|
class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
|
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||||
|
private let userSession: UserSessionProtocol
|
||||||
|
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
|
||||||
|
private let roomProxy: JoinedRoomProxyProtocol
|
||||||
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
|
private let appMediator: AppMediatorProtocol
|
||||||
|
private let emojiProvider: EmojiProviderProtocol
|
||||||
|
|
||||||
|
private let actionsSubject: PassthroughSubject<MediaEventsTimelineFlowCoordinatorAction, Never> = .init()
|
||||||
|
var actionsPublisher: AnyPublisher<MediaEventsTimelineFlowCoordinatorAction, Never> {
|
||||||
|
actionsSubject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
init(navigationStackCoordinator: NavigationStackCoordinator,
|
||||||
|
userSession: UserSessionProtocol,
|
||||||
|
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
|
||||||
|
roomProxy: JoinedRoomProxyProtocol,
|
||||||
|
userIndicatorController: UserIndicatorControllerProtocol,
|
||||||
|
appMediator: AppMediatorProtocol,
|
||||||
|
emojiProvider: EmojiProviderProtocol) {
|
||||||
|
self.navigationStackCoordinator = navigationStackCoordinator
|
||||||
|
self.userSession = userSession
|
||||||
|
self.roomTimelineControllerFactory = roomTimelineControllerFactory
|
||||||
|
self.roomProxy = roomProxy
|
||||||
|
self.userIndicatorController = userIndicatorController
|
||||||
|
self.appMediator = appMediator
|
||||||
|
self.emojiProvider = emojiProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
Task { await presentMediaEventsTimeline() }
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearRoute(animated: Bool) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func presentMediaEventsTimeline() async {
|
||||||
|
let timelineItemFactory = RoomTimelineItemFactory(userID: userSession.clientProxy.userID,
|
||||||
|
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||||
|
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userSession.clientProxy.userID))
|
||||||
|
|
||||||
|
guard case let .success(mediaTimelineController) = await roomTimelineControllerFactory.buildMessageFilteredRoomTimelineController(allowedMessageTypes: [.image, .video],
|
||||||
|
roomProxy: roomProxy,
|
||||||
|
timelineItemFactory: timelineItemFactory,
|
||||||
|
mediaProvider: userSession.mediaProvider) else {
|
||||||
|
MXLog.error("Failed presenting media timeline")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard case let .success(filesTimelineController) = await roomTimelineControllerFactory.buildMessageFilteredRoomTimelineController(allowedMessageTypes: [.file, .audio],
|
||||||
|
roomProxy: roomProxy,
|
||||||
|
timelineItemFactory: timelineItemFactory,
|
||||||
|
mediaProvider: userSession.mediaProvider) else {
|
||||||
|
MXLog.error("Failed presenting media timeline")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let parameters = MediaEventsTimelineScreenCoordinatorParameters(roomProxy: roomProxy,
|
||||||
|
mediaTimelineController: mediaTimelineController,
|
||||||
|
filesTimelineController: filesTimelineController,
|
||||||
|
mediaProvider: userSession.mediaProvider,
|
||||||
|
mediaPlayerProvider: MediaPlayerProvider(),
|
||||||
|
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
|
||||||
|
appMediator: appMediator,
|
||||||
|
emojiProvider: emojiProvider)
|
||||||
|
|
||||||
|
let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
navigationStackCoordinator.push(coordinator) { [weak self] in
|
||||||
|
self?.actionsSubject.send(.finished)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -65,7 +65,7 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||||
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||||
|
|
||||||
guard let timelineController = await roomTimelineControllerFactory.buildRoomPinnedTimelineController(roomProxy: roomProxy,
|
guard let timelineController = await roomTimelineControllerFactory.buildPinnedEventsRoomTimelineController(roomProxy: roomProxy,
|
||||||
timelineItemFactory: timelineItemFactory,
|
timelineItemFactory: timelineItemFactory,
|
||||||
mediaProvider: userSession.mediaProvider) else {
|
mediaProvider: userSession.mediaProvider) else {
|
||||||
fatalError("This can never fail because we allow this view to be presented only when the timeline is fully loaded and not nil")
|
fatalError("This can never fail because we allow this view to be presented only when the timeline is fully loaded and not nil")
|
||||||
|
@ -50,11 +50,6 @@ struct FocusEvent: Hashable {
|
|||||||
let shouldSetPin: Bool
|
let shouldSetPin: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum PinnedEventsTimelineSource: Hashable {
|
|
||||||
case room
|
|
||||||
case details(isRoot: Bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum PresentationAction: Hashable {
|
private enum PresentationAction: Hashable {
|
||||||
case eventFocus(FocusEvent)
|
case eventFocus(FocusEvent)
|
||||||
case share(ShareExtensionPayload)
|
case share(ShareExtensionPayload)
|
||||||
@ -102,6 +97,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// periphery:ignore - used to avoid deallocation
|
// periphery:ignore - used to avoid deallocation
|
||||||
private var pinnedEventsTimelineFlowCoordinator: PinnedEventsTimelineFlowCoordinator?
|
private var pinnedEventsTimelineFlowCoordinator: PinnedEventsTimelineFlowCoordinator?
|
||||||
// periphery:ignore - used to avoid deallocation
|
// periphery:ignore - used to avoid deallocation
|
||||||
|
private var mediaEventsTimelineFlowCoordinator: MediaEventsTimelineFlowCoordinator?
|
||||||
|
// periphery:ignore - used to avoid deallocation
|
||||||
private var childRoomFlowCoordinator: RoomFlowCoordinator?
|
private var childRoomFlowCoordinator: RoomFlowCoordinator?
|
||||||
|
|
||||||
private let stateMachine: StateMachine<State, Event> = .init(state: .initial)
|
private let stateMachine: StateMachine<State, Event> = .init(state: .initial)
|
||||||
@ -149,7 +146,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
fatalError("This flow coordinator expect a route")
|
fatalError("This flow coordinator expect a route")
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity
|
|
||||||
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||||
guard stateMachine.state != .complete else {
|
guard stateMachine.state != .complete else {
|
||||||
fatalError("This flow coordinator is `finished` ☠️")
|
fatalError("This flow coordinator is `finished` ☠️")
|
||||||
@ -369,16 +365,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
return .room
|
return .room
|
||||||
|
|
||||||
case (.room, .presentPinnedEventsTimeline):
|
case (.room, .presentPinnedEventsTimeline):
|
||||||
return .pinnedEventsTimeline(previousState: .room)
|
return .pinnedEventsTimeline(previousState: fromState)
|
||||||
case (.roomDetails(let isRoot), .presentPinnedEventsTimeline):
|
case (.roomDetails, .presentPinnedEventsTimeline):
|
||||||
return .pinnedEventsTimeline(previousState: .details(isRoot: isRoot))
|
return .pinnedEventsTimeline(previousState: fromState)
|
||||||
case (.pinnedEventsTimeline(let previousState), .dismissPinnedEventsTimeline):
|
case (.pinnedEventsTimeline(let previousState), .dismissPinnedEventsTimeline):
|
||||||
switch previousState {
|
return previousState
|
||||||
case .room:
|
|
||||||
return .room
|
|
||||||
case .details(let isRoot):
|
|
||||||
return .roomDetails(isRoot: isRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
case (.roomDetails, .presentPollsHistory):
|
case (.roomDetails, .presentPollsHistory):
|
||||||
return .pollsHistory
|
return .pollsHistory
|
||||||
@ -400,8 +391,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (.resolveSendFailure, .dismissResolveSendFailure):
|
case (.resolveSendFailure, .dismissResolveSendFailure):
|
||||||
return .room
|
return .room
|
||||||
|
|
||||||
// Child flow
|
|
||||||
|
|
||||||
case (_, .startChildFlow(let roomID, _, _)):
|
case (_, .startChildFlow(let roomID, _, _)):
|
||||||
return .presentingChild(childRoomID: roomID, previousState: fromState)
|
return .presentingChild(childRoomID: roomID, previousState: fromState)
|
||||||
case (.presentingChild(_, let previousState), .dismissChildFlow):
|
case (.presentingChild(_, let previousState), .dismissChildFlow):
|
||||||
@ -412,6 +401,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (.knockRequestsList(let previousState), .dismissKnockRequestsListScreen):
|
case (.knockRequestsList(let previousState), .dismissKnockRequestsListScreen):
|
||||||
return previousState
|
return previousState
|
||||||
|
|
||||||
|
case (.roomDetails, .presentMediaEventsTimeline):
|
||||||
|
return .mediaEventsTimeline(previousState: fromState)
|
||||||
|
case (.mediaEventsTimeline(let previousState), .dismissMediaEventsTimeline):
|
||||||
|
return previousState
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -527,7 +521,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case (.room, .presentPinnedEventsTimeline, .pinnedEventsTimeline):
|
case (.room, .presentPinnedEventsTimeline, .pinnedEventsTimeline):
|
||||||
presentPinnedEventsTimeline()
|
startPinnedEventsTimelineFlow()
|
||||||
case (.pinnedEventsTimeline, .dismissPinnedEventsTimeline, .room):
|
case (.pinnedEventsTimeline, .dismissPinnedEventsTimeline, .room):
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -537,7 +531,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case (.roomDetails, .presentPinnedEventsTimeline, .pinnedEventsTimeline):
|
case (.roomDetails, .presentPinnedEventsTimeline, .pinnedEventsTimeline):
|
||||||
presentPinnedEventsTimeline()
|
startPinnedEventsTimelineFlow()
|
||||||
case (.pinnedEventsTimeline, .dismissPinnedEventsTimeline, .roomDetails):
|
case (.pinnedEventsTimeline, .dismissPinnedEventsTimeline, .roomDetails):
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -573,6 +567,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (.knockRequestsList, .dismissKnockRequestsListScreen, .room):
|
case (.knockRequestsList, .dismissKnockRequestsListScreen, .room):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case (.roomDetails, .presentMediaEventsTimeline, .mediaEventsTimeline):
|
||||||
|
Task { await self.startMediaEventsTimelineFlow() }
|
||||||
|
case (.mediaEventsTimeline, .dismissMediaEventsTimeline, .roomDetails):
|
||||||
|
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) }
|
||||||
@ -848,6 +847,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.presentPinnedEventsTimeline)
|
stateMachine.tryEvent(.presentPinnedEventsTimeline)
|
||||||
case .presentKnockingRequestsListScreen:
|
case .presentKnockingRequestsListScreen:
|
||||||
stateMachine.tryEvent(.presentKnockRequestsListScreen)
|
stateMachine.tryEvent(.presentKnockRequestsListScreen)
|
||||||
|
case .presentMediaEventsTimeline:
|
||||||
|
stateMachine.tryEvent(.presentMediaEventsTimeline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@ -1432,44 +1433,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
coordinator.start()
|
coordinator.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentPinnedEventsTimeline() {
|
|
||||||
let stackCoordinator = NavigationStackCoordinator()
|
|
||||||
let coordinator = PinnedEventsTimelineFlowCoordinator(navigationStackCoordinator: stackCoordinator,
|
|
||||||
userSession: userSession,
|
|
||||||
roomTimelineControllerFactory: roomTimelineControllerFactory,
|
|
||||||
roomProxy: roomProxy,
|
|
||||||
userIndicatorController: userIndicatorController,
|
|
||||||
appMediator: appMediator,
|
|
||||||
emojiProvider: emojiProvider)
|
|
||||||
|
|
||||||
coordinator.actionsPublisher.sink { [weak self] action in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .finished:
|
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
|
||||||
case .displayUser(let userID):
|
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
|
||||||
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
|
|
||||||
case .forwardedMessageToRoom(let roomID):
|
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
|
||||||
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
|
||||||
case .displayRoomScreenWithFocussedPin(let eventID):
|
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
|
||||||
stateMachine.tryEvent(.presentRoom(presentationAction: .eventFocus(.init(eventID: eventID, shouldSetPin: true))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
pinnedEventsTimelineFlowCoordinator = coordinator
|
|
||||||
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
|
|
||||||
self?.stateMachine.tryEvent(.dismissPinnedEventsTimeline)
|
|
||||||
}
|
|
||||||
coordinator.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy) {
|
private func presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy) {
|
||||||
let coordinator = ResolveVerifiedUserSendFailureScreenCoordinator(parameters: .init(failure: failure,
|
let coordinator = ResolveVerifiedUserSendFailureScreenCoordinator(parameters: .init(failure: failure,
|
||||||
sendHandle: sendHandle,
|
sendHandle: sendHandle,
|
||||||
@ -1490,7 +1453,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Child Flow
|
// MARK: - Other flows
|
||||||
|
|
||||||
private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) async {
|
private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) async {
|
||||||
let coordinator = await RoomFlowCoordinator(roomID: roomID,
|
let coordinator = await RoomFlowCoordinator(roomID: roomID,
|
||||||
@ -1528,6 +1491,71 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
coordinator.handleAppRoute(.share(payload), animated: true)
|
coordinator.handleAppRoute(.share(payload), animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func startPinnedEventsTimelineFlow() {
|
||||||
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
|
let flowCoordinator = PinnedEventsTimelineFlowCoordinator(navigationStackCoordinator: stackCoordinator,
|
||||||
|
userSession: userSession,
|
||||||
|
roomTimelineControllerFactory: roomTimelineControllerFactory,
|
||||||
|
roomProxy: roomProxy,
|
||||||
|
userIndicatorController: userIndicatorController,
|
||||||
|
appMediator: appMediator,
|
||||||
|
emojiProvider: emojiProvider)
|
||||||
|
|
||||||
|
flowCoordinator.actionsPublisher.sink { [weak self] action in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .finished:
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
case .displayUser(let userID):
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
|
||||||
|
case .forwardedMessageToRoom(let roomID):
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
||||||
|
case .displayRoomScreenWithFocussedPin(let eventID):
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
stateMachine.tryEvent(.presentRoom(presentationAction: .eventFocus(.init(eventID: eventID, shouldSetPin: true))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
pinnedEventsTimelineFlowCoordinator = flowCoordinator
|
||||||
|
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
|
||||||
|
self?.stateMachine.tryEvent(.dismissPinnedEventsTimeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
flowCoordinator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startMediaEventsTimelineFlow() async {
|
||||||
|
let flowCoordinator = MediaEventsTimelineFlowCoordinator(navigationStackCoordinator: navigationStackCoordinator,
|
||||||
|
userSession: userSession,
|
||||||
|
roomTimelineControllerFactory: roomTimelineControllerFactory,
|
||||||
|
roomProxy: roomProxy,
|
||||||
|
userIndicatorController: userIndicatorController,
|
||||||
|
appMediator: appMediator,
|
||||||
|
emojiProvider: emojiProvider)
|
||||||
|
|
||||||
|
flowCoordinator.actionsPublisher.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .finished:
|
||||||
|
stateMachine.tryEvent(.dismissMediaEventsTimeline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
mediaEventsTimelineFlowCoordinator = flowCoordinator
|
||||||
|
|
||||||
|
flowCoordinator.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension RoomFlowCoordinator {
|
private extension RoomFlowCoordinator {
|
||||||
@ -1565,9 +1593,10 @@ private extension RoomFlowCoordinator {
|
|||||||
case pollsHistory
|
case pollsHistory
|
||||||
case pollsHistoryForm
|
case pollsHistoryForm
|
||||||
case rolesAndPermissions
|
case rolesAndPermissions
|
||||||
case pinnedEventsTimeline(previousState: PinnedEventsTimelineSource)
|
case pinnedEventsTimeline(previousState: State)
|
||||||
case resolveSendFailure
|
case resolveSendFailure
|
||||||
case knockRequestsList(previousState: State)
|
case knockRequestsList(previousState: State)
|
||||||
|
case mediaEventsTimeline(previousState: State)
|
||||||
|
|
||||||
/// A child flow is in progress.
|
/// A child flow is in progress.
|
||||||
case presentingChild(childRoomID: String, previousState: State)
|
case presentingChild(childRoomID: String, previousState: State)
|
||||||
@ -1643,12 +1672,14 @@ private extension RoomFlowCoordinator {
|
|||||||
case presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
|
case presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy)
|
||||||
case dismissResolveSendFailure
|
case dismissResolveSendFailure
|
||||||
|
|
||||||
// Child room flow events
|
|
||||||
case startChildFlow(roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint)
|
case startChildFlow(roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint)
|
||||||
case dismissChildFlow
|
case dismissChildFlow
|
||||||
|
|
||||||
case presentKnockRequestsListScreen
|
case presentKnockRequestsListScreen
|
||||||
case dismissKnockRequestsListScreen
|
case dismissKnockRequestsListScreen
|
||||||
|
|
||||||
|
case presentMediaEventsTimeline
|
||||||
|
case dismissMediaEventsTimeline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6147,6 +6147,76 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol {
|
|||||||
return timelineFocusedOnEventEventIDNumberOfEventsReturnValue
|
return timelineFocusedOnEventEventIDNumberOfEventsReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - messageFilteredTimeline
|
||||||
|
|
||||||
|
var messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount = 0
|
||||||
|
var messageFilteredTimelineAllowedMessageTypesCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
messageFilteredTimelineAllowedMessageTypesUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var messageFilteredTimelineAllowedMessageTypesCalled: Bool {
|
||||||
|
return messageFilteredTimelineAllowedMessageTypesCallsCount > 0
|
||||||
|
}
|
||||||
|
var messageFilteredTimelineAllowedMessageTypesReceivedAllowedMessageTypes: [RoomMessageEventMessageType]?
|
||||||
|
var messageFilteredTimelineAllowedMessageTypesReceivedInvocations: [[RoomMessageEventMessageType]] = []
|
||||||
|
|
||||||
|
var messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue: Result<TimelineProxyProtocol, RoomProxyError>!
|
||||||
|
var messageFilteredTimelineAllowedMessageTypesReturnValue: Result<TimelineProxyProtocol, RoomProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<TimelineProxyProtocol, RoomProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
messageFilteredTimelineAllowedMessageTypesUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var messageFilteredTimelineAllowedMessageTypesClosure: (([RoomMessageEventMessageType]) async -> Result<TimelineProxyProtocol, RoomProxyError>)?
|
||||||
|
|
||||||
|
func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType]) async -> Result<TimelineProxyProtocol, RoomProxyError> {
|
||||||
|
messageFilteredTimelineAllowedMessageTypesCallsCount += 1
|
||||||
|
messageFilteredTimelineAllowedMessageTypesReceivedAllowedMessageTypes = allowedMessageTypes
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.messageFilteredTimelineAllowedMessageTypesReceivedInvocations.append(allowedMessageTypes)
|
||||||
|
}
|
||||||
|
if let messageFilteredTimelineAllowedMessageTypesClosure = messageFilteredTimelineAllowedMessageTypesClosure {
|
||||||
|
return await messageFilteredTimelineAllowedMessageTypesClosure(allowedMessageTypes)
|
||||||
|
} else {
|
||||||
|
return messageFilteredTimelineAllowedMessageTypesReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - redact
|
//MARK: - redact
|
||||||
|
|
||||||
var redactUnderlyingCallsCount = 0
|
var redactUnderlyingCallsCount = 0
|
||||||
@ -12602,17 +12672,17 @@ class RoomTimelineControllerFactoryMock: RoomTimelineControllerFactoryProtocol {
|
|||||||
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue
|
return buildRoomTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - buildRoomPinnedTimelineController
|
//MARK: - buildPinnedEventsRoomTimelineController
|
||||||
|
|
||||||
var buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
|
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
|
||||||
var buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount: Int {
|
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount: Int {
|
||||||
get {
|
get {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
return buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
|
return buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
|
||||||
} else {
|
} else {
|
||||||
var returnValue: Int? = nil
|
var returnValue: Int? = nil
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
returnValue = buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
|
returnValue = buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue!
|
return returnValue!
|
||||||
@ -12620,29 +12690,29 @@ class RoomTimelineControllerFactoryMock: RoomTimelineControllerFactoryProtocol {
|
|||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
|
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
|
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCalled: Bool {
|
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCalled: Bool {
|
||||||
return buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount > 0
|
return buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount > 0
|
||||||
}
|
}
|
||||||
var buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
|
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
|
||||||
var buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
|
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
|
||||||
|
|
||||||
var buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: RoomTimelineControllerProtocol?
|
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: RoomTimelineControllerProtocol?
|
||||||
var buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue: RoomTimelineControllerProtocol? {
|
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue: RoomTimelineControllerProtocol? {
|
||||||
get {
|
get {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
return buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
|
return buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
|
||||||
} else {
|
} else {
|
||||||
var returnValue: RoomTimelineControllerProtocol?? = nil
|
var returnValue: RoomTimelineControllerProtocol?? = nil
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
returnValue = buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
|
returnValue = buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue!
|
return returnValue!
|
||||||
@ -12650,26 +12720,96 @@ class RoomTimelineControllerFactoryMock: RoomTimelineControllerFactoryProtocol {
|
|||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
|
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
|
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure: ((JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> RoomTimelineControllerProtocol?)?
|
var buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure: ((JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> RoomTimelineControllerProtocol?)?
|
||||||
|
|
||||||
func buildRoomPinnedTimelineController(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol? {
|
func buildPinnedEventsRoomTimelineController(roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol? {
|
||||||
buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
|
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
|
||||||
buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
|
buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations.append((roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
|
self.buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations.append((roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
|
||||||
}
|
}
|
||||||
if let buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure = buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure {
|
if let buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure = buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure {
|
||||||
return await buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure(roomProxy, timelineItemFactory, mediaProvider)
|
return await buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderClosure(roomProxy, timelineItemFactory, mediaProvider)
|
||||||
} else {
|
} else {
|
||||||
return buildRoomPinnedTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue
|
return buildPinnedEventsRoomTimelineControllerRoomProxyTimelineItemFactoryMediaProviderReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//MARK: - buildMessageFilteredRoomTimelineController
|
||||||
|
|
||||||
|
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = 0
|
||||||
|
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderCalled: Bool {
|
||||||
|
return buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderCallsCount > 0
|
||||||
|
}
|
||||||
|
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReceivedArguments: (allowedMessageTypes: [RoomMessageEventMessageType], roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)?
|
||||||
|
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations: [(allowedMessageTypes: [RoomMessageEventMessageType], roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol)] = []
|
||||||
|
|
||||||
|
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue: Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>!
|
||||||
|
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReturnValue: Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderClosure: (([RoomMessageEventMessageType], JoinedRoomProxyProtocol, RoomTimelineItemFactoryProtocol, MediaProviderProtocol) async -> Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>)?
|
||||||
|
|
||||||
|
func buildMessageFilteredRoomTimelineController(allowedMessageTypes: [RoomMessageEventMessageType], roomProxy: JoinedRoomProxyProtocol, timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol) async -> Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError> {
|
||||||
|
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderCallsCount += 1
|
||||||
|
buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReceivedArguments = (allowedMessageTypes: allowedMessageTypes, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReceivedInvocations.append((allowedMessageTypes: allowedMessageTypes, roomProxy: roomProxy, timelineItemFactory: timelineItemFactory, mediaProvider: mediaProvider))
|
||||||
|
}
|
||||||
|
if let buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderClosure = buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderClosure {
|
||||||
|
return await buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderClosure(allowedMessageTypes, roomProxy, timelineItemFactory, mediaProvider)
|
||||||
|
} else {
|
||||||
|
return buildMessageFilteredRoomTimelineControllerAllowedMessageTypesRoomProxyTimelineItemFactoryMediaProviderReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8248,6 +8248,71 @@ open class MediaSourceSDKMock: MatrixRustSDK.MediaSource {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - toJson
|
||||||
|
|
||||||
|
var toJsonUnderlyingCallsCount = 0
|
||||||
|
open var toJsonCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return toJsonUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = toJsonUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
toJsonUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
toJsonUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var toJsonCalled: Bool {
|
||||||
|
return toJsonCallsCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var toJsonUnderlyingReturnValue: String!
|
||||||
|
open var toJsonReturnValue: String! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return toJsonUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: String? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = toJsonUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
toJsonUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
toJsonUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var toJsonClosure: (() -> String)?
|
||||||
|
|
||||||
|
open override func toJson() -> String {
|
||||||
|
toJsonCallsCount += 1
|
||||||
|
if let toJsonClosure = toJsonClosure {
|
||||||
|
return toJsonClosure()
|
||||||
|
} else {
|
||||||
|
return toJsonReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - url
|
//MARK: - url
|
||||||
|
|
||||||
var urlUnderlyingCallsCount = 0
|
var urlUnderlyingCallsCount = 0
|
||||||
@ -12813,6 +12878,81 @@ open class RoomSDKMock: MatrixRustSDK.Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - messageFilteredTimeline
|
||||||
|
|
||||||
|
open var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesThrowableError: Error?
|
||||||
|
var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingCallsCount = 0
|
||||||
|
open var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesCalled: Bool {
|
||||||
|
return messageFilteredTimelineInternalIdPrefixAllowedMessageTypesCallsCount > 0
|
||||||
|
}
|
||||||
|
open var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesReceivedArguments: (internalIdPrefix: String?, allowedMessageTypes: [RoomMessageEventMessageType])?
|
||||||
|
open var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesReceivedInvocations: [(internalIdPrefix: String?, allowedMessageTypes: [RoomMessageEventMessageType])] = []
|
||||||
|
|
||||||
|
var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingReturnValue: Timeline!
|
||||||
|
open var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesReturnValue: Timeline! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Timeline? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
messageFilteredTimelineInternalIdPrefixAllowedMessageTypesUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var messageFilteredTimelineInternalIdPrefixAllowedMessageTypesClosure: ((String?, [RoomMessageEventMessageType]) async throws -> Timeline)?
|
||||||
|
|
||||||
|
open override func messageFilteredTimeline(internalIdPrefix: String?, allowedMessageTypes: [RoomMessageEventMessageType]) async throws -> Timeline {
|
||||||
|
if let error = messageFilteredTimelineInternalIdPrefixAllowedMessageTypesThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
messageFilteredTimelineInternalIdPrefixAllowedMessageTypesCallsCount += 1
|
||||||
|
messageFilteredTimelineInternalIdPrefixAllowedMessageTypesReceivedArguments = (internalIdPrefix: internalIdPrefix, allowedMessageTypes: allowedMessageTypes)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.messageFilteredTimelineInternalIdPrefixAllowedMessageTypesReceivedInvocations.append((internalIdPrefix: internalIdPrefix, allowedMessageTypes: allowedMessageTypes))
|
||||||
|
}
|
||||||
|
if let messageFilteredTimelineInternalIdPrefixAllowedMessageTypesClosure = messageFilteredTimelineInternalIdPrefixAllowedMessageTypesClosure {
|
||||||
|
return try await messageFilteredTimelineInternalIdPrefixAllowedMessageTypesClosure(internalIdPrefix, allowedMessageTypes)
|
||||||
|
} else {
|
||||||
|
return messageFilteredTimelineInternalIdPrefixAllowedMessageTypesReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - ownUserId
|
//MARK: - ownUserId
|
||||||
|
|
||||||
var ownUserIdUnderlyingCallsCount = 0
|
var ownUserIdUnderlyingCallsCount = 0
|
||||||
|
@ -10,6 +10,7 @@ import SwiftUI
|
|||||||
extension MediaProviderMock {
|
extension MediaProviderMock {
|
||||||
struct Configuration { }
|
struct Configuration { }
|
||||||
|
|
||||||
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
convenience init(configuration: Configuration) {
|
convenience init(configuration: Configuration) {
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
|
@ -106,12 +106,12 @@ struct VoiceMessageButton_Previews: PreviewProvider, TestablePreview {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
VoiceMessageButton(state: .paused, size: .small, action: { })
|
VoiceMessageButton(state: .paused, size: .small) { }
|
||||||
VoiceMessageButton(state: .paused, size: .medium, action: { })
|
VoiceMessageButton(state: .paused, size: .medium) { }
|
||||||
}
|
}
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
VoiceMessageButton(state: .playing, size: .small, action: { })
|
VoiceMessageButton(state: .playing, size: .small) { }
|
||||||
VoiceMessageButton(state: .playing, size: .medium, action: { })
|
VoiceMessageButton(state: .playing, size: .medium) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
@ -34,7 +34,7 @@ struct BlockedUsersScreen: View {
|
|||||||
ForEach(context.viewState.blockedUsers, id: \.self) { user in
|
ForEach(context.viewState.blockedUsers, id: \.self) { user in
|
||||||
ListRow(label: .avatar(title: user.displayName ?? user.userID, icon: avatar(for: user)),
|
ListRow(label: .avatar(title: user.displayName ?? user.userID, icon: avatar(for: user)),
|
||||||
details: .isWaiting(context.viewState.processingUserID == user.userID),
|
details: .isWaiting(context.viewState.processingUserID == user.userID),
|
||||||
kind: .button(action: { context.send(viewAction: .unblockUser(user)) }))
|
kind: .button { context.send(viewAction: .unblockUser(user)) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,14 +34,14 @@ class PollFormScreenViewModel: PollFormScreenViewModelType, PollFormScreenViewMo
|
|||||||
title: L10n.screenEditPollDeleteConfirmationTitle,
|
title: L10n.screenEditPollDeleteConfirmationTitle,
|
||||||
message: L10n.screenEditPollDeleteConfirmation,
|
message: L10n.screenEditPollDeleteConfirmation,
|
||||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionOk, action: { self.actionsSubject.send(.delete) }))
|
secondaryButton: .init(title: L10n.actionOk) { self.actionsSubject.send(.delete) })
|
||||||
case .cancel:
|
case .cancel:
|
||||||
if state.formContentHasChanged {
|
if state.formContentHasChanged {
|
||||||
state.bindings.alertInfo = .init(id: .init(),
|
state.bindings.alertInfo = .init(id: .init(),
|
||||||
title: L10n.screenCreatePollCancelConfirmationTitleIos,
|
title: L10n.screenCreatePollCancelConfirmationTitleIos,
|
||||||
message: L10n.screenCreatePollCancelConfirmationContentIos,
|
message: L10n.screenCreatePollCancelConfirmationContentIos,
|
||||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionOk, action: { self.actionsSubject.send(.cancel) }))
|
secondaryButton: .init(title: L10n.actionOk) { self.actionsSubject.send(.cancel) })
|
||||||
} else {
|
} else {
|
||||||
actionsSubject.send(.cancel)
|
actionsSubject.send(.cancel)
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,9 @@ class DeactivateAccountScreenViewModel: DeactivateAccountScreenViewModelType, De
|
|||||||
state.bindings.alertInfo = .init(id: .confirmation,
|
state.bindings.alertInfo = .init(id: .confirmation,
|
||||||
title: L10n.screenDeactivateAccountTitle,
|
title: L10n.screenDeactivateAccountTitle,
|
||||||
message: L10n.screenDeactivateAccountConfirmationDialogContent,
|
message: L10n.screenDeactivateAccountConfirmationDialogContent,
|
||||||
primaryButton: .init(title: L10n.actionDeactivate, action: {
|
primaryButton: .init(title: L10n.actionDeactivate) {
|
||||||
Task { await self.deactivateAccount() }
|
Task { await self.deactivateAccount() }
|
||||||
}),
|
},
|
||||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,10 +38,10 @@ class EncryptionResetScreenViewModel: EncryptionResetScreenViewModelType, Encryp
|
|||||||
state.bindings.alertInfo = .init(id: UUID(),
|
state.bindings.alertInfo = .init(id: UUID(),
|
||||||
title: L10n.screenResetEncryptionConfirmationAlertTitle,
|
title: L10n.screenResetEncryptionConfirmationAlertTitle,
|
||||||
message: L10n.screenResetEncryptionConfirmationAlertSubtitle,
|
message: L10n.screenResetEncryptionConfirmationAlertSubtitle,
|
||||||
primaryButton: .init(title: L10n.screenResetEncryptionConfirmationAlertAction, role: .destructive, action: { [weak self] in
|
primaryButton: .init(title: L10n.screenResetEncryptionConfirmationAlertAction, role: .destructive) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
Task { await self.startResetFlow() }
|
Task { await self.startResetFlow() }
|
||||||
}))
|
})
|
||||||
case .cancel:
|
case .cancel:
|
||||||
actionsSubject.send(.cancel)
|
actionsSubject.send(.cancel)
|
||||||
}
|
}
|
||||||
|
@ -326,11 +326,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
self.state.bindings.alertInfo = AlertInfo(id: UUID(),
|
self.state.bindings.alertInfo = AlertInfo(id: UUID(),
|
||||||
title: L10n.bannerMigrateToNativeSlidingSyncForceLogoutTitle,
|
title: L10n.bannerMigrateToNativeSlidingSyncForceLogoutTitle,
|
||||||
primaryButton: .init(title: L10n.bannerMigrateToNativeSlidingSyncAction,
|
primaryButton: .init(title: L10n.bannerMigrateToNativeSlidingSyncAction) { [weak self] in
|
||||||
action: { [weak self] in
|
|
||||||
self?.appSettings.slidingSyncDiscovery = .native
|
self?.appSettings.slidingSyncDiscovery = .native
|
||||||
self?.actionsSubject.send(.logoutWithoutConfirmation)
|
self?.actionsSubject.send(.logoutWithoutConfirmation)
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,7 +432,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
title: title,
|
title: title,
|
||||||
message: message,
|
message: message,
|
||||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionDecline, role: .destructive, action: { Task { await self.declineInvite(roomID: room.id) } }))
|
secondaryButton: .init(title: L10n.actionDecline, role: .destructive) { Task { await self.declineInvite(roomID: room.id) } })
|
||||||
}
|
}
|
||||||
|
|
||||||
private func declineInvite(roomID: String) async {
|
private func declineInvite(roomID: String) async {
|
||||||
|
@ -63,7 +63,7 @@ struct HomeScreenEmptyStateLayout: Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
||||||
let mainView = subviews.first(where: { $0.priority > 0 })
|
let mainView = subviews.first { $0.priority > 0 }
|
||||||
let topViews = subviews.filter { $0 != mainView }
|
let topViews = subviews.filter { $0 != mainView }
|
||||||
|
|
||||||
var y: CGFloat = bounds.minY
|
var y: CGFloat = bounds.minY
|
||||||
|
@ -50,7 +50,7 @@ struct InviteUsersScreenSelectedItem_Previews: PreviewProvider, TestablePreview
|
|||||||
ScrollView(.horizontal) {
|
ScrollView(.horizontal) {
|
||||||
HStack(spacing: 28) {
|
HStack(spacing: 28) {
|
||||||
ForEach(people, id: \.userID) { user in
|
ForEach(people, id: \.userID) { user in
|
||||||
InviteUsersScreenSelectedItem(user: user, mediaProvider: MediaProviderMock(configuration: .init()), dismissAction: { })
|
InviteUsersScreenSelectedItem(user: user, mediaProvider: MediaProviderMock(configuration: .init())) { }
|
||||||
.frame(width: 72)
|
.frame(width: 72)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
|
|||||||
title: L10n.screenInvitesDeclineChatTitle,
|
title: L10n.screenInvitesDeclineChatTitle,
|
||||||
message: L10n.screenInvitesDeclineChatMessage(roomName),
|
message: L10n.screenInvitesDeclineChatMessage(roomName),
|
||||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionDecline, role: .destructive, action: { Task { await self.declineInvite() } }))
|
secondaryButton: .init(title: L10n.actionDecline, role: .destructive) { Task { await self.declineInvite() } })
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showCancelKnockConfirmationAlert() {
|
private func showCancelKnockConfirmationAlert() {
|
||||||
@ -229,7 +229,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
|
|||||||
title: L10n.screenJoinRoomCancelKnockAlertTitle,
|
title: L10n.screenJoinRoomCancelKnockAlertTitle,
|
||||||
message: L10n.screenJoinRoomCancelKnockAlertDescription,
|
message: L10n.screenJoinRoomCancelKnockAlertDescription,
|
||||||
primaryButton: .init(title: L10n.actionNo, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionNo, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.screenJoinRoomCancelKnockAlertConfirmation, role: .destructive, action: { Task { await self.cancelKnock() } }))
|
secondaryButton: .init(title: L10n.screenJoinRoomCancelKnockAlertConfirmation, role: .destructive) { Task { await self.cancelKnock() } })
|
||||||
}
|
}
|
||||||
|
|
||||||
private func declineInvite() async {
|
private func declineInvite() async {
|
||||||
|
@ -261,7 +261,7 @@ struct JoinRoomScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
topic: "“Science and technology were the only keys to opening the door to the future, and people approached science with the faith and sincerity of elementary school students.”",
|
topic: "“Science and technology were the only keys to opening the door to the future, and people approached science with the faith and sincerity of elementary school students.”",
|
||||||
avatarURL: .mockMXCAvatar,
|
avatarURL: .mockMXCAvatar,
|
||||||
memberCount: UInt(100),
|
memberCount: UInt(100),
|
||||||
isHistoryWorldReadable: false,
|
isHistoryWorldReadable: nil,
|
||||||
isJoined: membership.isJoined,
|
isJoined: membership.isJoined,
|
||||||
isInvited: membership.isInvited,
|
isInvited: membership.isInvited,
|
||||||
isPublic: membership.isPublic,
|
isPublic: membership.isPublic,
|
||||||
|
@ -177,19 +177,19 @@ struct KnockRequestCell_Previews: PreviewProvider, TestablePreview {
|
|||||||
static let aliceWithNoName = KnockRequestCellInfo(id: "@alice:matrix.org", displayName: nil, avatarURL: nil, timestamp: "20 Nov 2024", reason: nil)
|
static let aliceWithNoName = KnockRequestCellInfo(id: "@alice:matrix.org", displayName: nil, avatarURL: nil, timestamp: "20 Nov 2024", reason: nil)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
KnockRequestCell(cellInfo: aliceWithLongReason, onAccept: { _ in }, onDecline: { _ in }, onDeclineAndBan: { _ in })
|
KnockRequestCell(cellInfo: aliceWithLongReason) { _ in } onDecline: { _ in } onDeclineAndBan: { _ in }
|
||||||
.previewDisplayName("Long reason")
|
.previewDisplayName("Long reason")
|
||||||
KnockRequestCell(cellInfo: aliceWithShortReason, onAccept: { _ in }, onDecline: { _ in }, onDeclineAndBan: { _ in })
|
KnockRequestCell(cellInfo: aliceWithShortReason) { _ in } onDecline: { _ in } onDeclineAndBan: { _ in }
|
||||||
.previewDisplayName("Short reason")
|
.previewDisplayName("Short reason")
|
||||||
KnockRequestCell(cellInfo: aliceWithNoReason, onAccept: { _ in }, onDecline: { _ in }, onDeclineAndBan: { _ in })
|
KnockRequestCell(cellInfo: aliceWithNoReason) { _ in } onDecline: { _ in } onDeclineAndBan: { _ in }
|
||||||
.previewDisplayName("No reason")
|
.previewDisplayName("No reason")
|
||||||
KnockRequestCell(cellInfo: aliceWithNoName, onAccept: { _ in }, onDecline: { _ in }, onDeclineAndBan: { _ in })
|
KnockRequestCell(cellInfo: aliceWithNoName) { _ in } onDecline: { _ in } onDeclineAndBan: { _ in }
|
||||||
.previewDisplayName("No name")
|
.previewDisplayName("No name")
|
||||||
KnockRequestCell(cellInfo: aliceWithShortReason, onAccept: nil, onDecline: { _ in }, onDeclineAndBan: { _ in })
|
// KnockRequestCell(cellInfo: aliceWithShortReason, onAccept: nil) onDecline: { _ in } onDeclineAndBan: { _ in }
|
||||||
.previewDisplayName("No Accept")
|
// .previewDisplayName("No Accept")
|
||||||
KnockRequestCell(cellInfo: aliceWithShortReason, onAccept: nil, onDecline: nil, onDeclineAndBan: { _ in })
|
// KnockRequestCell(cellInfo: aliceWithShortReason) onDeclineAndBan: { _ in }
|
||||||
.previewDisplayName("No Accept and Decline")
|
// .previewDisplayName("No Accept and Decline")
|
||||||
KnockRequestCell(cellInfo: aliceWithShortReason, onAccept: { _ in }, onDecline: { _ in }, onDeclineAndBan: nil)
|
// KnockRequestCell(cellInfo: aliceWithShortReason) { _ in } onDecline: { _ in })
|
||||||
.previewDisplayName("No Ban")
|
// .previewDisplayName("No Ban")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
// Please see LICENSE in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MediaEventsTimelineScreenCoordinatorParameters {
|
||||||
|
let roomProxy: JoinedRoomProxyProtocol
|
||||||
|
let mediaTimelineController: RoomTimelineControllerProtocol
|
||||||
|
let filesTimelineController: RoomTimelineControllerProtocol
|
||||||
|
let mediaProvider: MediaProviderProtocol
|
||||||
|
let mediaPlayerProvider: MediaPlayerProviderProtocol
|
||||||
|
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
||||||
|
let appMediator: AppMediatorProtocol
|
||||||
|
let emojiProvider: EmojiProviderProtocol
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MediaEventsTimelineScreenCoordinatorAction { }
|
||||||
|
|
||||||
|
final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
||||||
|
private let parameters: MediaEventsTimelineScreenCoordinatorParameters
|
||||||
|
private let viewModel: MediaEventsTimelineScreenViewModelProtocol
|
||||||
|
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private let actionsSubject: PassthroughSubject<MediaEventsTimelineScreenCoordinatorAction, Never> = .init()
|
||||||
|
var actions: AnyPublisher<MediaEventsTimelineScreenCoordinatorAction, Never> {
|
||||||
|
actionsSubject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(parameters: MediaEventsTimelineScreenCoordinatorParameters) {
|
||||||
|
self.parameters = parameters
|
||||||
|
|
||||||
|
let mediaTimelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
||||||
|
timelineController: parameters.mediaTimelineController,
|
||||||
|
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)
|
||||||
|
|
||||||
|
let filesTimelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
||||||
|
timelineController: parameters.filesTimelineController,
|
||||||
|
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)
|
||||||
|
|
||||||
|
viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
|
||||||
|
filesTimelineViewModel: filesTimelineViewModel,
|
||||||
|
mediaProvider: parameters.mediaProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPresentable() -> AnyView {
|
||||||
|
AnyView(MediaEventsTimelineScreen(context: viewModel.context))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
// Please see LICENSE in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum MediaEventsTimelineScreenViewModelAction { }
|
||||||
|
|
||||||
|
enum MediaEventsTimelineScreenMode {
|
||||||
|
case media
|
||||||
|
case files
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MediaEventsTimelineScreenViewState: BindableState {
|
||||||
|
var isBackPaginating = false
|
||||||
|
var items = [RoomTimelineItemViewState]()
|
||||||
|
|
||||||
|
var bindings: MediaEventsTimelineScreenViewStateBindings
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MediaEventsTimelineScreenViewStateBindings {
|
||||||
|
var screenMode: MediaEventsTimelineScreenMode
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MediaEventsTimelineScreenViewAction {
|
||||||
|
case changedScreenMode
|
||||||
|
case oldestItemDidAppear
|
||||||
|
case oldestItemDidDisappear
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
// Please see LICENSE in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
typealias MediaEventsTimelineScreenViewModelType = StateStoreViewModel<MediaEventsTimelineScreenViewState, MediaEventsTimelineScreenViewAction>
|
||||||
|
|
||||||
|
class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType, MediaEventsTimelineScreenViewModelProtocol {
|
||||||
|
private let mediaTimelineViewModel: TimelineViewModelProtocol
|
||||||
|
private let filesTimelineViewModel: TimelineViewModelProtocol
|
||||||
|
|
||||||
|
private var isOldestItemVisible = false
|
||||||
|
|
||||||
|
private var activeTimelineViewModel: TimelineViewModelProtocol {
|
||||||
|
switch state.bindings.screenMode {
|
||||||
|
case .media:
|
||||||
|
mediaTimelineViewModel
|
||||||
|
case .files:
|
||||||
|
filesTimelineViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let actionsSubject: PassthroughSubject<MediaEventsTimelineScreenViewModelAction, Never> = .init()
|
||||||
|
var actionsPublisher: AnyPublisher<MediaEventsTimelineScreenViewModelAction, Never> {
|
||||||
|
actionsSubject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(mediaTimelineViewModel: TimelineViewModelProtocol,
|
||||||
|
filesTimelineViewModel: TimelineViewModelProtocol,
|
||||||
|
mediaProvider: MediaProviderProtocol,
|
||||||
|
screenMode: MediaEventsTimelineScreenMode = .media) {
|
||||||
|
self.mediaTimelineViewModel = mediaTimelineViewModel
|
||||||
|
self.filesTimelineViewModel = filesTimelineViewModel
|
||||||
|
|
||||||
|
super.init(initialViewState: .init(bindings: .init(screenMode: screenMode)), mediaProvider: mediaProvider)
|
||||||
|
|
||||||
|
mediaTimelineViewModel.context.$viewState.sink { [weak self] timelineViewState in
|
||||||
|
guard let self, state.bindings.screenMode == .media else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWithTimelineViewState(timelineViewState)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
filesTimelineViewModel.context.$viewState.sink { [weak self] timelineViewState in
|
||||||
|
guard let self, state.bindings.screenMode == .files else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWithTimelineViewState(timelineViewState)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
updateWithTimelineViewState(activeTimelineViewModel.context.viewState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
override func process(viewAction: MediaEventsTimelineScreenViewAction) {
|
||||||
|
MXLog.info("View model: received view action: \(viewAction)")
|
||||||
|
|
||||||
|
switch viewAction {
|
||||||
|
case .changedScreenMode:
|
||||||
|
updateWithTimelineViewState(activeTimelineViewModel.context.viewState)
|
||||||
|
case .oldestItemDidAppear:
|
||||||
|
isOldestItemVisible = true
|
||||||
|
backPaginateIfNecessary(paginationStatus: activeTimelineViewModel.context.viewState.timelineState.paginationState.backward)
|
||||||
|
case .oldestItemDidDisappear:
|
||||||
|
isOldestItemVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func updateWithTimelineViewState(_ timelineViewState: TimelineViewState) {
|
||||||
|
state.items = timelineViewState.timelineState.itemViewStates.filter { itemViewState in
|
||||||
|
switch itemViewState.type {
|
||||||
|
case .image, .video:
|
||||||
|
state.bindings.screenMode == .media
|
||||||
|
case .audio, .file:
|
||||||
|
state.bindings.screenMode == .files
|
||||||
|
default:
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}.reversed()
|
||||||
|
|
||||||
|
state.isBackPaginating = (timelineViewState.timelineState.paginationState.backward == .paginating)
|
||||||
|
backPaginateIfNecessary(paginationStatus: timelineViewState.timelineState.paginationState.backward)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func backPaginateIfNecessary(paginationStatus: PaginationStatus) {
|
||||||
|
if paginationStatus == .idle, isOldestItemVisible {
|
||||||
|
activeTimelineViewModel.context.send(viewAction: .paginateBackwards)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
// Please see LICENSE in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
protocol MediaEventsTimelineScreenViewModelProtocol {
|
||||||
|
var actionsPublisher: AnyPublisher<MediaEventsTimelineScreenViewModelAction, Never> { get }
|
||||||
|
var context: MediaEventsTimelineScreenViewModelType.Context { get }
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022-2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
// Please see LICENSE in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MediaEventsTimelineScreen: View {
|
||||||
|
@ObservedObject var context: MediaEventsTimelineScreenViewModel.Context
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.background(.compound.bgCanvasDefault)
|
||||||
|
// Doesn't play well with the transformed scrollView
|
||||||
|
.toolbarBackground(.visible, for: .navigationBar)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
Picker("", selection: $context.screenMode) {
|
||||||
|
Text(L10n.screenMediaBrowserListModeMedia)
|
||||||
|
.padding()
|
||||||
|
.tag(MediaEventsTimelineScreenMode.media)
|
||||||
|
Text(L10n.screenMediaBrowserListModeFiles)
|
||||||
|
.padding()
|
||||||
|
.tag(MediaEventsTimelineScreenMode.files)
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var content: some View {
|
||||||
|
ScrollView {
|
||||||
|
Group {
|
||||||
|
let columns = [GridItem(.adaptive(minimum: 80, maximum: 150), spacing: 1)]
|
||||||
|
LazyVGrid(columns: columns, alignment: .center, spacing: 1) {
|
||||||
|
ForEach(context.viewState.items) { item in
|
||||||
|
Color.clear // Let the image aspect fill in place
|
||||||
|
.aspectRatio(1, contentMode: .fill)
|
||||||
|
.overlay {
|
||||||
|
viewForTimelineItem(item)
|
||||||
|
}
|
||||||
|
.clipped()
|
||||||
|
.scaleEffect(.init(width: 1, height: -1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needs to be wrapped in a LazyStack otherwise appearance calls don't trigger
|
||||||
|
LazyVStack(spacing: 0) {
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: 44)
|
||||||
|
.foregroundStyle(.compound.bgCanvasDefault)
|
||||||
|
.overlay {
|
||||||
|
if context.viewState.isBackPaginating {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
context.send(viewAction: .oldestItemDidAppear)
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
context.send(viewAction: .oldestItemDidDisappear)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scaleEffect(.init(width: 1, height: -1))
|
||||||
|
.onChange(of: context.screenMode) { _, _ in
|
||||||
|
context.send(viewAction: .changedScreenMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder func viewForTimelineItem(_ item: RoomTimelineItemViewState) -> some View {
|
||||||
|
switch item.type {
|
||||||
|
case .image(let timelineItem):
|
||||||
|
#warning("Make this work for gifs")
|
||||||
|
LoadableImage(mediaSource: timelineItem.content.thumbnailInfo?.source ?? timelineItem.content.imageInfo.source,
|
||||||
|
mediaType: .timelineItem(uniqueID: timelineItem.id.uniqueID.id),
|
||||||
|
blurhash: timelineItem.content.blurhash,
|
||||||
|
size: timelineItem.content.thumbnailInfo?.size ?? timelineItem.content.imageInfo.size,
|
||||||
|
mediaProvider: context.mediaProvider) {
|
||||||
|
placeholder
|
||||||
|
}
|
||||||
|
.mediaItemAspectRatio(imageInfo: timelineItem.content.thumbnailInfo ?? timelineItem.content.imageInfo)
|
||||||
|
case .video(let timelineItem):
|
||||||
|
if let thumbnailSource = timelineItem.content.thumbnailInfo?.source {
|
||||||
|
LoadableImage(mediaSource: thumbnailSource,
|
||||||
|
mediaType: .timelineItem(uniqueID: timelineItem.id.uniqueID.id),
|
||||||
|
blurhash: timelineItem.content.blurhash,
|
||||||
|
size: timelineItem.content.thumbnailInfo?.size,
|
||||||
|
mediaProvider: context.mediaProvider) { imageView in
|
||||||
|
imageView
|
||||||
|
.overlay { playIcon }
|
||||||
|
} placeholder: {
|
||||||
|
placeholder
|
||||||
|
}
|
||||||
|
.mediaItemAspectRatio(imageInfo: timelineItem.content.thumbnailInfo)
|
||||||
|
} else {
|
||||||
|
playIcon
|
||||||
|
}
|
||||||
|
case .separator(let timelineItem):
|
||||||
|
Text(timelineItem.text)
|
||||||
|
default:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var playIcon: some View {
|
||||||
|
Image(systemName: "play.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 50, height: 50)
|
||||||
|
.background(.ultraThinMaterial, in: Circle())
|
||||||
|
.foregroundColor(.white)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var placeholder: some View {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(.compound._bgBubbleIncoming)
|
||||||
|
.opacity(0.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
/// Constrains the max height of a media item in the timeline, whilst preserving its aspect ratio.
|
||||||
|
@ViewBuilder
|
||||||
|
func mediaItemAspectRatio(imageInfo: ImageInfoProxy?) -> some View {
|
||||||
|
aspectRatio(imageInfo?.aspectRatio, contentMode: .fill)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
|
static let timelineViewModel: TimelineViewModel = {
|
||||||
|
let timelineController = MockRoomTimelineController(timelineKind: .media)
|
||||||
|
return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")),
|
||||||
|
timelineController: timelineController,
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
|
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||||
|
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||||
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
|
appMediator: AppMediatorMock.default,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings))
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let mediaViewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: timelineViewModel,
|
||||||
|
filesTimelineViewModel: timelineViewModel,
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
|
screenMode: .media)
|
||||||
|
|
||||||
|
static let filesViewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: timelineViewModel,
|
||||||
|
filesTimelineViewModel: timelineViewModel,
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
|
screenMode: .files)
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
NavigationStack {
|
||||||
|
MediaEventsTimelineScreen(context: mediaViewModel.context)
|
||||||
|
.previewDisplayName("Media")
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationStack {
|
||||||
|
MediaEventsTimelineScreen(context: filesViewModel.context)
|
||||||
|
.previewDisplayName("Files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -69,7 +69,7 @@ struct PinnedEventsTimelineScreen: View {
|
|||||||
TimelineView()
|
TimelineView()
|
||||||
.id(timelineContext.viewState.roomID)
|
.id(timelineContext.viewState.roomID)
|
||||||
.environmentObject(timelineContext)
|
.environmentObject(timelineContext)
|
||||||
.environment(\.focussedEventID, timelineContext.viewState.timelineViewState.focussedEvent?.eventID)
|
.environment(\.focussedEventID, timelineContext.viewState.timelineState.focussedEvent?.eventID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,8 +60,7 @@ struct RoomChangeRolesScreenSelectedItem_Previews: PreviewProvider, TestablePrev
|
|||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
ForEach(members, id: \.id) { member in
|
ForEach(members, id: \.id) { member in
|
||||||
RoomChangeRolesScreenSelectedItem(member: member,
|
RoomChangeRolesScreenSelectedItem(member: member,
|
||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init())) { }
|
||||||
dismissAction: { })
|
|
||||||
.frame(width: 72)
|
.frame(width: 72)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ enum RoomDetailsScreenCoordinatorAction {
|
|||||||
case presentRolesAndPermissionsScreen
|
case presentRolesAndPermissionsScreen
|
||||||
case presentCall
|
case presentCall
|
||||||
case presentPinnedEventsTimeline
|
case presentPinnedEventsTimeline
|
||||||
|
case presentMediaEventsTimeline
|
||||||
case presentKnockingRequestsListScreen
|
case presentKnockingRequestsListScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +81,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.presentCall)
|
actionsSubject.send(.presentCall)
|
||||||
case .displayPinnedEventsTimeline:
|
case .displayPinnedEventsTimeline:
|
||||||
actionsSubject.send(.presentPinnedEventsTimeline)
|
actionsSubject.send(.presentPinnedEventsTimeline)
|
||||||
|
case .displayMediaEventsTimeline:
|
||||||
|
actionsSubject.send(.presentMediaEventsTimeline)
|
||||||
case .displayKnockingRequests:
|
case .displayKnockingRequests:
|
||||||
actionsSubject.send(.presentKnockingRequestsListScreen)
|
actionsSubject.send(.presentKnockingRequestsListScreen)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ enum RoomDetailsScreenViewModelAction {
|
|||||||
case requestRolesAndPermissionsPresentation
|
case requestRolesAndPermissionsPresentation
|
||||||
case startCall
|
case startCall
|
||||||
case displayPinnedEventsTimeline
|
case displayPinnedEventsTimeline
|
||||||
|
case displayMediaEventsTimeline
|
||||||
case displayKnockingRequests
|
case displayKnockingRequests
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,13 +49,15 @@ struct RoomDetailsScreenViewState: BindableState {
|
|||||||
var notificationSettingsState: RoomDetailsNotificationSettingsState = .loading
|
var notificationSettingsState: RoomDetailsNotificationSettingsState = .loading
|
||||||
var canJoinCall = false
|
var canJoinCall = false
|
||||||
var pinnedEventsActionState = RoomDetailsScreenPinnedEventsActionState.loading
|
var pinnedEventsActionState = RoomDetailsScreenPinnedEventsActionState.loading
|
||||||
|
|
||||||
var knockingEnabled = false
|
var knockingEnabled = false
|
||||||
var isKnockableRoom = false
|
var isKnockableRoom = false
|
||||||
|
|
||||||
var canSeeKnockingRequests: Bool {
|
var canSeeKnockingRequests: Bool {
|
||||||
knockingEnabled && dmRecipient == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers)
|
knockingEnabled && dmRecipient == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mediaBrowserEnabled = false
|
||||||
|
|
||||||
var canEdit: Bool {
|
var canEdit: Bool {
|
||||||
!isDirect && (canEditRoomName || canEditRoomTopic || canEditRoomAvatar)
|
!isDirect && (canEditRoomName || canEditRoomTopic || canEditRoomAvatar)
|
||||||
}
|
}
|
||||||
@ -197,6 +200,7 @@ enum RoomDetailsScreenViewAction {
|
|||||||
case processTapRolesAndPermissions
|
case processTapRolesAndPermissions
|
||||||
case processTapCall
|
case processTapCall
|
||||||
case processTapPinnedEvents
|
case processTapPinnedEvents
|
||||||
|
case processTapMediaEvents
|
||||||
case processTapRequestsToJoin
|
case processTapRequestsToJoin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +79,10 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
.weakAssign(to: \.state.knockingEnabled, on: self)
|
.weakAssign(to: \.state.knockingEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
appSettings.$mediaBrowserEnabled
|
||||||
|
.weakAssign(to: \.state.mediaBrowserEnabled, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
appMediator.networkMonitor.reachabilityPublisher
|
appMediator.networkMonitor.reachabilityPublisher
|
||||||
.filter { $0 == .reachable }
|
.filter { $0 == .reachable }
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
@ -164,6 +168,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
case .processTapPinnedEvents:
|
case .processTapPinnedEvents:
|
||||||
analyticsService.trackInteraction(name: .PinnedMessageRoomInfoButton)
|
analyticsService.trackInteraction(name: .PinnedMessageRoomInfoButton)
|
||||||
actionsSubject.send(.displayPinnedEventsTimeline)
|
actionsSubject.send(.displayPinnedEventsTimeline)
|
||||||
|
case .processTapMediaEvents:
|
||||||
|
actionsSubject.send(.displayMediaEventsTimeline)
|
||||||
case .processTapRequestsToJoin:
|
case .processTapRequestsToJoin:
|
||||||
actionsSubject.send(.displayKnockingRequests)
|
actionsSubject.send(.displayKnockingRequests)
|
||||||
}
|
}
|
||||||
@ -207,8 +213,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self, ownUserID = roomProxy.ownUserID] members in
|
.sink { [weak self, ownUserID = roomProxy.ownUserID] members in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
let accountOwner = members.first(where: { $0.userID == ownUserID })
|
let accountOwner = members.first { $0.userID == ownUserID }
|
||||||
let dmRecipient = members.first(where: { $0.userID != ownUserID })
|
let dmRecipient = members.first { $0.userID != ownUserID }
|
||||||
self.dmRecipient = dmRecipient
|
self.dmRecipient = dmRecipient
|
||||||
self.state.dmRecipient = dmRecipient.map(RoomMemberDetails.init(withProxy:))
|
self.state.dmRecipient = dmRecipient.map(RoomMemberDetails.init(withProxy:))
|
||||||
self.state.accountOwner = accountOwner.map(RoomMemberDetails.init(withProxy:))
|
self.state.accountOwner = accountOwner.map(RoomMemberDetails.init(withProxy:))
|
||||||
|
@ -164,9 +164,9 @@ struct RoomDetailsScreen: View {
|
|||||||
ListRow(label: .default(title: L10n.screenRoomDetailsPinnedEventsRowTitle,
|
ListRow(label: .default(title: L10n.screenRoomDetailsPinnedEventsRowTitle,
|
||||||
icon: \.pin),
|
icon: \.pin),
|
||||||
details: context.viewState.pinnedEventsActionState.isLoading ? .isWaiting(true) : .title(context.viewState.pinnedEventsActionState.count),
|
details: context.viewState.pinnedEventsActionState.isLoading ? .isWaiting(true) : .title(context.viewState.pinnedEventsActionState.count),
|
||||||
kind: context.viewState.pinnedEventsActionState.isLoading ? .label : .navigationLink(action: {
|
kind: context.viewState.pinnedEventsActionState.isLoading ? .label : .navigationLink {
|
||||||
context.send(viewAction: .processTapPinnedEvents)
|
context.send(viewAction: .processTapPinnedEvents)
|
||||||
}))
|
})
|
||||||
.disabled(context.viewState.pinnedEventsActionState.isLoading)
|
.disabled(context.viewState.pinnedEventsActionState.isLoading)
|
||||||
|
|
||||||
if context.viewState.canSeeKnockingRequests {
|
if context.viewState.canSeeKnockingRequests {
|
||||||
@ -184,6 +184,13 @@ struct RoomDetailsScreen: View {
|
|||||||
context.send(viewAction: .processTapPolls)
|
context.send(viewAction: .processTapPolls)
|
||||||
})
|
})
|
||||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.pollsHistory)
|
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.pollsHistory)
|
||||||
|
|
||||||
|
if context.viewState.mediaBrowserEnabled {
|
||||||
|
ListRow(label: .default(title: L10n.screenMediaBrowserTitle, icon: \.image),
|
||||||
|
kind: .navigationLink {
|
||||||
|
context.send(viewAction: .processTapMediaEvents)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +46,7 @@ struct RoomMemberDetailsScreen: View {
|
|||||||
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
||||||
isVerified: context.viewState.showVerifiedBadge,
|
isVerified: context.viewState.showVerifiedBadge,
|
||||||
avatarSize: .user(on: .memberDetails),
|
avatarSize: .user(on: .memberDetails),
|
||||||
mediaProvider: context.mediaProvider,
|
mediaProvider: context.mediaProvider) { }
|
||||||
footer: { })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,14 +586,14 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
private func makeCreateWithTextAlertInfo(urlBinding: Binding<String>, textBinding: Binding<String>) -> AlertInfo<UUID> {
|
private func makeCreateWithTextAlertInfo(urlBinding: Binding<String>, textBinding: Binding<String>) -> AlertInfo<UUID> {
|
||||||
AlertInfo(id: UUID(),
|
AlertInfo(id: UUID(),
|
||||||
title: L10n.richTextEditorCreateLink,
|
title: L10n.richTextEditorCreateLink,
|
||||||
primaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionCancel, action: {
|
primaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionCancel) {
|
||||||
self.restoreComposerSelectedRange()
|
self.restoreComposerSelectedRange()
|
||||||
}),
|
},
|
||||||
secondaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionSave, action: {
|
secondaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionSave) {
|
||||||
self.restoreComposerSelectedRange()
|
self.restoreComposerSelectedRange()
|
||||||
self.createLinkWithText()
|
self.createLinkWithText()
|
||||||
|
|
||||||
}),
|
},
|
||||||
textFields: [AlertInfo<UUID>.AlertTextField(placeholder: L10n.commonText,
|
textFields: [AlertInfo<UUID>.AlertTextField(placeholder: L10n.commonText,
|
||||||
text: textBinding,
|
text: textBinding,
|
||||||
autoCapitalization: .never,
|
autoCapitalization: .never,
|
||||||
@ -607,14 +607,14 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
private func makeSetUrlAlertInfo(urlBinding: Binding<String>, isEdit: Bool) -> AlertInfo<UUID> {
|
private func makeSetUrlAlertInfo(urlBinding: Binding<String>, isEdit: Bool) -> AlertInfo<UUID> {
|
||||||
AlertInfo(id: UUID(),
|
AlertInfo(id: UUID(),
|
||||||
title: isEdit ? L10n.richTextEditorEditLink : L10n.richTextEditorCreateLink,
|
title: isEdit ? L10n.richTextEditorEditLink : L10n.richTextEditorCreateLink,
|
||||||
primaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionCancel, action: {
|
primaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionCancel) {
|
||||||
self.restoreComposerSelectedRange()
|
self.restoreComposerSelectedRange()
|
||||||
}),
|
},
|
||||||
secondaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionSave, action: {
|
secondaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionSave) {
|
||||||
self.restoreComposerSelectedRange()
|
self.restoreComposerSelectedRange()
|
||||||
self.setLink()
|
self.setLink()
|
||||||
|
|
||||||
}),
|
},
|
||||||
textFields: [AlertInfo<UUID>.AlertTextField(placeholder: L10n.richTextEditorUrlPlaceholder,
|
textFields: [AlertInfo<UUID>.AlertTextField(placeholder: L10n.richTextEditorUrlPlaceholder,
|
||||||
text: urlBinding,
|
text: urlBinding,
|
||||||
autoCapitalization: .never,
|
autoCapitalization: .never,
|
||||||
@ -624,16 +624,16 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
private func makeEditChoiceAlertInfo(urlBinding: Binding<String>) -> AlertInfo<UUID> {
|
private func makeEditChoiceAlertInfo(urlBinding: Binding<String>) -> AlertInfo<UUID> {
|
||||||
AlertInfo(id: UUID(),
|
AlertInfo(id: UUID(),
|
||||||
title: L10n.richTextEditorEditLink,
|
title: L10n.richTextEditorEditLink,
|
||||||
primaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionRemove, role: .destructive, action: {
|
primaryButton: AlertInfo<UUID>.AlertButton(title: L10n.actionRemove, role: .destructive) {
|
||||||
self.restoreComposerSelectedRange()
|
self.restoreComposerSelectedRange()
|
||||||
self.removeLinks()
|
self.removeLinks()
|
||||||
}),
|
},
|
||||||
verticalButtons: [AlertInfo<UUID>.AlertButton(title: L10n.actionEdit, action: {
|
verticalButtons: [AlertInfo<UUID>.AlertButton(title: L10n.actionEdit) {
|
||||||
self.state.bindings.alertInfo = nil
|
self.state.bindings.alertInfo = nil
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.state.bindings.alertInfo = self.makeSetUrlAlertInfo(urlBinding: urlBinding, isEdit: true)
|
self.state.bindings.alertInfo = self.makeSetUrlAlertInfo(urlBinding: urlBinding, isEdit: true)
|
||||||
}
|
}
|
||||||
})])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
private func restoreComposerSelectedRange() {
|
private func restoreComposerSelectedRange() {
|
||||||
|
@ -102,9 +102,9 @@ private struct SingleKnockRequestBannerContent: View {
|
|||||||
Button(L10n.screenRoomSingleKnockRequestViewButtonTitle, action: onViewAll)
|
Button(L10n.screenRoomSingleKnockRequestViewButtonTitle, action: onViewAll)
|
||||||
.buttonStyle(.compound(.secondary, size: .medium))
|
.buttonStyle(.compound(.secondary, size: .medium))
|
||||||
if let onAccept {
|
if let onAccept {
|
||||||
Button(L10n.screenRoomSingleKnockRequestAcceptButtonTitle, action: {
|
Button(L10n.screenRoomSingleKnockRequestAcceptButtonTitle) {
|
||||||
onAccept(request.userID)
|
onAccept(request.userID)
|
||||||
})
|
}
|
||||||
.buttonStyle(.compound(.primary, size: .medium))
|
.buttonStyle(.compound(.primary, size: .medium))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,9 +166,9 @@ private struct KnockRequestsBannerDismissButton: View {
|
|||||||
CompoundIcon(\.close, size: .medium, relativeTo: .compound.bodySMSemibold)
|
CompoundIcon(\.close, size: .medium, relativeTo: .compound.bodySMSemibold)
|
||||||
.foregroundColor(.compound.iconTertiary)
|
.foregroundColor(.compound.iconTertiary)
|
||||||
}
|
}
|
||||||
.alignmentGuide(.top, computeValue: { _ in
|
.alignmentGuide(.top) { _ in
|
||||||
3
|
3
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,15 +188,16 @@ struct KnockRequestsBannerView_Previews: PreviewProvider, TestablePreview {
|
|||||||
]
|
]
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
KnockRequestsBannerView(requests: singleRequest, onDismiss: { }, onAccept: { _ in }, onViewAll: { })
|
KnockRequestsBannerView(requests: singleRequest) { } onAccept: { _ in } onViewAll: { }
|
||||||
.previewDisplayName("Single Request")
|
.previewDisplayName("Single Request")
|
||||||
|
// swiftlint:disable:next trailing_closure
|
||||||
KnockRequestsBannerView(requests: singleRequest, onDismiss: { }, onAccept: nil, onViewAll: { })
|
KnockRequestsBannerView(requests: singleRequest, onDismiss: { }, onAccept: nil, onViewAll: { })
|
||||||
.previewDisplayName("Single Request, no accept action")
|
.previewDisplayName("Single Request, no accept action")
|
||||||
KnockRequestsBannerView(requests: singleRequestWithReason, onDismiss: { }, onAccept: { _ in }, onViewAll: { })
|
KnockRequestsBannerView(requests: singleRequestWithReason) { } onAccept: { _ in } onViewAll: { }
|
||||||
.previewDisplayName("Single Request with reason")
|
.previewDisplayName("Single Request with reason")
|
||||||
KnockRequestsBannerView(requests: singleRequestNoDisplayName, onDismiss: { }, onAccept: { _ in }, onViewAll: { })
|
KnockRequestsBannerView(requests: singleRequestNoDisplayName) { } onAccept: { _ in } onViewAll: { }
|
||||||
.previewDisplayName("Single Request, No Display Name")
|
.previewDisplayName("Single Request, No Display Name")
|
||||||
KnockRequestsBannerView(requests: multipleRequests, onDismiss: { }, onAccept: { _ in }, onViewAll: { })
|
KnockRequestsBannerView(requests: multipleRequests) { } onAccept: { _ in } onViewAll: { }
|
||||||
.previewDisplayName("Multiple Requests")
|
.previewDisplayName("Multiple Requests")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ struct RoomScreen: View {
|
|||||||
TimelineView()
|
TimelineView()
|
||||||
.id(timelineContext.viewState.roomID)
|
.id(timelineContext.viewState.roomID)
|
||||||
.environmentObject(timelineContext)
|
.environmentObject(timelineContext)
|
||||||
.environment(\.focussedEventID, timelineContext.viewState.timelineViewState.focussedEvent?.eventID)
|
.environment(\.focussedEventID, timelineContext.viewState.timelineState.focussedEvent?.eventID)
|
||||||
.overlay(alignment: .bottomTrailing) {
|
.overlay(alignment: .bottomTrailing) {
|
||||||
scrollToBottomButton
|
scrollToBottomButton
|
||||||
}
|
}
|
||||||
@ -183,7 +183,7 @@ struct RoomScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var isAtBottomAndLive: Bool {
|
private var isAtBottomAndLive: Bool {
|
||||||
timelineContext.isScrolledToBottom && timelineContext.viewState.timelineViewState.isLive
|
timelineContext.isScrolledToBottom && timelineContext.viewState.timelineState.isLive
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -51,6 +51,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
|
|||||||
var elementCallBaseURLOverride: URL? { get set }
|
var elementCallBaseURLOverride: URL? { get set }
|
||||||
var knockingEnabled: Bool { get set }
|
var knockingEnabled: Bool { get set }
|
||||||
var createMediaCaptionsEnabled: Bool { get set }
|
var createMediaCaptionsEnabled: Bool { get set }
|
||||||
|
var mediaBrowserEnabled: Bool { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppSettings: DeveloperOptionsProtocol { }
|
extension AppSettings: DeveloperOptionsProtocol { }
|
||||||
|
@ -49,7 +49,7 @@ struct DeveloperOptionsScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Timeline") {
|
Section("Room") {
|
||||||
Toggle(isOn: $context.hideTimelineMedia) {
|
Toggle(isOn: $context.hideTimelineMedia) {
|
||||||
Text("Hide image & video previews")
|
Text("Hide image & video previews")
|
||||||
}
|
}
|
||||||
@ -57,6 +57,10 @@ struct DeveloperOptionsScreen: View {
|
|||||||
Toggle(isOn: $context.createMediaCaptionsEnabled) {
|
Toggle(isOn: $context.createMediaCaptionsEnabled) {
|
||||||
Text("Allow creation of media captions")
|
Text("Allow creation of media captions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $context.mediaBrowserEnabled) {
|
||||||
|
Text("Enable the media browser")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Join rules") {
|
Section("Join rules") {
|
||||||
|
@ -123,7 +123,7 @@ class NotificationSettingsEditScreenViewModel: NotificationSettingsEditScreenVie
|
|||||||
guard !Task.isCancelled else { return }
|
guard !Task.isCancelled else { return }
|
||||||
|
|
||||||
let filteredRoomsSummary = roomSummaryProvider.roomListPublisher.value.filter { summary in
|
let filteredRoomsSummary = roomSummaryProvider.roomListPublisher.value.filter { summary in
|
||||||
roomsWithUserDefinedRules.contains(where: { summary.id == $0 })
|
roomsWithUserDefinedRules.contains { summary.id == $0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomsWithUserDefinedMode: [NotificationSettingsEditScreenRoom] = []
|
var roomsWithUserDefinedMode: [NotificationSettingsEditScreenRoom] = []
|
||||||
@ -142,7 +142,7 @@ class NotificationSettingsEditScreenViewModel: NotificationSettingsEditScreenVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort the room list
|
// Sort the room list
|
||||||
roomsWithUserDefinedMode.sort(by: { $0.name.localizedCompare($1.name) == .orderedAscending })
|
roomsWithUserDefinedMode.sort { $0.name.localizedCompare($1.name) == .orderedAscending }
|
||||||
|
|
||||||
state.roomsWithUserDefinedMode = roomsWithUserDefinedMode
|
state.roomsWithUserDefinedMode = roomsWithUserDefinedMode
|
||||||
}
|
}
|
||||||
|
@ -391,7 +391,6 @@ class TimelineInteractionHandler {
|
|||||||
|
|
||||||
// MARK: Audio Playback
|
// MARK: Audio Playback
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity
|
|
||||||
func playPauseAudio(for itemID: TimelineItemIdentifier) async {
|
func playPauseAudio(for itemID: TimelineItemIdentifier) async {
|
||||||
MXLog.info("Toggle play/pause audio for itemID \(itemID)")
|
MXLog.info("Toggle play/pause audio for itemID \(itemID)")
|
||||||
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID) else {
|
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID) else {
|
||||||
|
@ -92,7 +92,7 @@ struct TimelineViewState: BindableState {
|
|||||||
var showLoading = false
|
var showLoading = false
|
||||||
var showReadReceipts = false
|
var showReadReceipts = false
|
||||||
var isEncryptedOneToOneRoom = false
|
var isEncryptedOneToOneRoom = false
|
||||||
var timelineViewState: TimelineState // check the doc before changing this
|
var timelineState: TimelineState // check the doc before changing this
|
||||||
|
|
||||||
var ownUserID: String
|
var ownUserID: String
|
||||||
var canCurrentUserRedactOthers = false
|
var canCurrentUserRedactOthers = false
|
||||||
|
@ -78,7 +78,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
super.init(initialViewState: TimelineViewState(isPinnedEventsTimeline: timelineController.timelineKind == .pinned,
|
super.init(initialViewState: TimelineViewState(isPinnedEventsTimeline: timelineController.timelineKind == .pinned,
|
||||||
roomID: roomProxy.id,
|
roomID: roomProxy.id,
|
||||||
isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom,
|
isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom,
|
||||||
timelineViewState: TimelineState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
|
timelineState: TimelineState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }),
|
||||||
ownUserID: roomProxy.ownUserID,
|
ownUserID: roomProxy.ownUserID,
|
||||||
isViewSourceEnabled: appSettings.viewSourceEnabled,
|
isViewSourceEnabled: appSettings.viewSourceEnabled,
|
||||||
isCreateMediaCaptionsEnabled: appSettings.createMediaCaptionsEnabled,
|
isCreateMediaCaptionsEnabled: appSettings.createMediaCaptionsEnabled,
|
||||||
@ -107,6 +107,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
return self.timelineInteractionHandler.audioPlayerState(for: itemID)
|
return self.timelineInteractionHandler.audioPlayerState(for: itemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.timelineState.paginationState = timelineController.paginationState
|
||||||
buildTimelineViews(timelineItems: timelineController.timelineItems)
|
buildTimelineViews(timelineItems: timelineController.timelineItems)
|
||||||
|
|
||||||
updateMembers(roomProxy.membersPublisher.value)
|
updateMembers(roomProxy.membersPublisher.value)
|
||||||
@ -174,7 +175,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
case .scrolledToFocussedItem:
|
case .scrolledToFocussedItem:
|
||||||
didScrollToFocussedItem()
|
didScrollToFocussedItem()
|
||||||
case .hasSwitchedTimeline:
|
case .hasSwitchedTimeline:
|
||||||
Task { state.timelineViewState.isSwitchingTimelines = false }
|
Task { state.timelineState.isSwitchingTimelines = false }
|
||||||
case let .hasScrolled(direction):
|
case let .hasScrolled(direction):
|
||||||
actionsSubject.send(.hasScrolled(direction: direction))
|
actionsSubject.send(.hasScrolled(direction: direction))
|
||||||
case .setOpenURLAction(let action):
|
case .setOpenURLAction(let action):
|
||||||
@ -215,8 +216,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func focusOnEvent(eventID: String) async {
|
func focusOnEvent(eventID: String) async {
|
||||||
if state.timelineViewState.hasLoadedItem(with: eventID) {
|
if state.timelineState.hasLoadedItem(with: eventID) {
|
||||||
state.timelineViewState.focussedEvent = .init(eventID: eventID, appearance: .animated)
|
state.timelineState.focussedEvent = .init(eventID: eventID, appearance: .animated)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +226,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
|
|
||||||
switch await timelineController.focusOnEvent(eventID, timelineSize: Constants.detachedTimelineSize) {
|
switch await timelineController.focusOnEvent(eventID, timelineSize: Constants.detachedTimelineSize) {
|
||||||
case .success:
|
case .success:
|
||||||
state.timelineViewState.focussedEvent = .init(eventID: eventID, appearance: .immediate)
|
state.timelineState.focussedEvent = .init(eventID: eventID, appearance: .immediate)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
MXLog.error("Failed to focus on event \(eventID)")
|
MXLog.error("Failed to focus on event \(eventID)")
|
||||||
|
|
||||||
@ -244,9 +245,9 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func didScrollToFocussedItem() {
|
private func didScrollToFocussedItem() {
|
||||||
if var focussedEvent = state.timelineViewState.focussedEvent {
|
if var focussedEvent = state.timelineState.focussedEvent {
|
||||||
focussedEvent.appearance = .hasAppeared
|
focussedEvent.appearance = .hasAppeared
|
||||||
state.timelineViewState.focussedEvent = focussedEvent
|
state.timelineState.focussedEvent = focussedEvent
|
||||||
hideFocusLoadingIndicator()
|
hideFocusLoadingIndicator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,16 +363,16 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
case .updatedTimelineItems(let updatedItems, let isSwitchingTimelines):
|
case .updatedTimelineItems(let updatedItems, let isSwitchingTimelines):
|
||||||
buildTimelineViews(timelineItems: updatedItems, isSwitchingTimelines: isSwitchingTimelines)
|
buildTimelineViews(timelineItems: updatedItems, isSwitchingTimelines: isSwitchingTimelines)
|
||||||
case .paginationState(let paginationState):
|
case .paginationState(let paginationState):
|
||||||
if state.timelineViewState.paginationState != paginationState {
|
if state.timelineState.paginationState != paginationState {
|
||||||
state.timelineViewState.paginationState = paginationState
|
state.timelineState.paginationState = paginationState
|
||||||
}
|
}
|
||||||
case .isLive(let isLive):
|
case .isLive(let isLive):
|
||||||
if state.timelineViewState.isLive != isLive {
|
if state.timelineState.isLive != isLive {
|
||||||
state.timelineViewState.isLive = isLive
|
state.timelineState.isLive = isLive
|
||||||
|
|
||||||
// Remove the event highlight *only* when transitioning from non-live to live.
|
// Remove the event highlight *only* when transitioning from non-live to live.
|
||||||
if isLive, state.timelineViewState.focussedEvent != nil {
|
if isLive, state.timelineState.focussedEvent != nil {
|
||||||
state.timelineViewState.focussedEvent = nil
|
state.timelineState.focussedEvent = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -516,7 +517,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.timelineViewState.paginationState.forward == .timelineEndReached {
|
if state.timelineState.paginationState.forward == .timelineEndReached {
|
||||||
focusLive()
|
focusLive()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,8 +526,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func scrollToBottom() {
|
private func scrollToBottom() {
|
||||||
if state.timelineViewState.isLive {
|
if state.timelineState.isLive {
|
||||||
state.timelineViewState.scrollToBottomPublisher.send(())
|
state.timelineState.scrollToBottomPublisher.send(())
|
||||||
} else {
|
} else {
|
||||||
focusLive()
|
focusLive()
|
||||||
}
|
}
|
||||||
@ -703,14 +704,14 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isSwitchingTimelines {
|
if isSwitchingTimelines {
|
||||||
state.timelineViewState.isSwitchingTimelines = true
|
state.timelineState.isSwitchingTimelines = true
|
||||||
}
|
}
|
||||||
|
|
||||||
state.timelineViewState.itemsDictionary = timelineItemsDictionary
|
state.timelineState.itemsDictionary = timelineItemsDictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateViewState(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewState {
|
private func updateViewState(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewState {
|
||||||
if let timelineItemViewState = state.timelineViewState.itemsDictionary[item.id.uniqueID] {
|
if let timelineItemViewState = state.timelineState.itemsDictionary[item.id.uniqueID] {
|
||||||
timelineItemViewState.groupStyle = groupStyle
|
timelineItemViewState.groupStyle = groupStyle
|
||||||
timelineItemViewState.type = .init(item: item)
|
timelineItemViewState.type = .init(item: item)
|
||||||
return timelineItemViewState
|
return timelineItemViewState
|
||||||
@ -744,7 +745,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
userIndicatorController.alertInfo = .init(id: .init(),
|
userIndicatorController.alertInfo = .init(id: .init(),
|
||||||
title: L10n.screenRoomInviteAgainAlertTitle,
|
title: L10n.screenRoomInviteAgainAlertTitle,
|
||||||
message: L10n.screenRoomInviteAgainAlertMessage,
|
message: L10n.screenRoomInviteAgainAlertMessage,
|
||||||
primaryButton: .init(title: L10n.actionInvite, action: { [weak self] in self?.inviteOtherDMUserBack() }),
|
primaryButton: .init(title: L10n.actionInvite) { [weak self] in self?.inviteOtherDMUserBack() },
|
||||||
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -830,14 +831,14 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
state.bindings.alertInfo = .init(id: type,
|
state.bindings.alertInfo = .init(id: type,
|
||||||
title: L10n.dialogPermissionMicrophoneTitleIos(InfoPlistReader.main.bundleDisplayName),
|
title: L10n.dialogPermissionMicrophoneTitleIos(InfoPlistReader.main.bundleDisplayName),
|
||||||
message: L10n.dialogPermissionMicrophoneDescriptionIos,
|
message: L10n.dialogPermissionMicrophoneDescriptionIos,
|
||||||
primaryButton: .init(title: L10n.commonSettings, action: { [weak self] in self?.appMediator.openAppSettings() }),
|
primaryButton: .init(title: L10n.commonSettings) { [weak self] in self?.appMediator.openAppSettings() },
|
||||||
secondaryButton: .init(title: L10n.actionNotNow, role: .cancel, action: nil))
|
secondaryButton: .init(title: L10n.actionNotNow, role: .cancel, action: nil))
|
||||||
case .pollEndConfirmation(let pollStartID):
|
case .pollEndConfirmation(let pollStartID):
|
||||||
state.bindings.alertInfo = .init(id: type,
|
state.bindings.alertInfo = .init(id: type,
|
||||||
title: L10n.actionEndPoll,
|
title: L10n.actionEndPoll,
|
||||||
message: L10n.commonPollEndConfirmation,
|
message: L10n.commonPollEndConfirmation,
|
||||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionOk, action: { self.timelineInteractionHandler.endPoll(pollStartID: pollStartID) }))
|
secondaryButton: .init(title: L10n.actionOk) { self.timelineInteractionHandler.endPoll(pollStartID: pollStartID) })
|
||||||
case .sendingFailed:
|
case .sendingFailed:
|
||||||
state.bindings.alertInfo = .init(id: type,
|
state.bindings.alertInfo = .init(id: type,
|
||||||
title: L10n.commonSendingFailed,
|
title: L10n.commonSendingFailed,
|
||||||
|
@ -400,7 +400,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
static var mockTimeline: some View {
|
static var mockTimeline: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(viewModel.state.timelineViewState.itemViewStates) { viewState in
|
ForEach(viewModel.state.timelineState.itemViewStates) { viewState in
|
||||||
RoomTimelineItemView(viewState: viewState)
|
RoomTimelineItemView(viewState: viewState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
static let sendingLast: TextRoomTimelineItem = {
|
static let sendingLast: TextRoomTimelineItem = {
|
||||||
let id = viewModel.state.timelineViewState.uniqueIDs.last ?? .init(id: UUID().uuidString)
|
let id = viewModel.state.timelineState.uniqueIDs.last ?? .init(id: UUID().uuidString)
|
||||||
var result = TextRoomTimelineItem(id: .event(uniqueID: id, eventOrTransactionID: .eventId(eventId: UUID().uuidString)),
|
var result = TextRoomTimelineItem(id: .event(uniqueID: id, eventOrTransactionID: .eventId(eventId: UUID().uuidString)),
|
||||||
timestamp: .mock,
|
timestamp: .mock,
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
@ -99,7 +99,7 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
static let sentLast: TextRoomTimelineItem = {
|
static let sentLast: TextRoomTimelineItem = {
|
||||||
let id = viewModel.state.timelineViewState.uniqueIDs.last ?? .init(id: UUID().uuidString)
|
let id = viewModel.state.timelineState.uniqueIDs.last ?? .init(id: UUID().uuidString)
|
||||||
let result = TextRoomTimelineItem(id: .event(uniqueID: id, eventOrTransactionID: .eventId(eventId: UUID().uuidString)),
|
let result = TextRoomTimelineItem(id: .event(uniqueID: id, eventOrTransactionID: .eventId(eventId: UUID().uuidString)),
|
||||||
timestamp: .mock,
|
timestamp: .mock,
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
|
@ -14,7 +14,7 @@ struct TimelineItemStatusView: View {
|
|||||||
@EnvironmentObject private var context: TimelineViewModel.Context
|
@EnvironmentObject private var context: TimelineViewModel.Context
|
||||||
|
|
||||||
private var isLastOutgoingMessage: Bool {
|
private var isLastOutgoingMessage: Bool {
|
||||||
timelineItem.isOutgoing && context.viewState.timelineViewState.uniqueIDs.last == timelineItem.id.uniqueID
|
timelineItem.isOutgoing && context.viewState.timelineState.uniqueIDs.last == timelineItem.id.uniqueID
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -16,7 +16,7 @@ struct TimelineView: UIViewControllerRepresentable {
|
|||||||
func makeUIViewController(context: Context) -> TimelineTableViewController {
|
func makeUIViewController(context: Context) -> TimelineTableViewController {
|
||||||
let tableViewController = TimelineTableViewController(coordinator: context.coordinator,
|
let tableViewController = TimelineTableViewController(coordinator: context.coordinator,
|
||||||
isScrolledToBottom: $viewModelContext.isScrolledToBottom,
|
isScrolledToBottom: $viewModelContext.isScrolledToBottom,
|
||||||
scrollToBottomPublisher: viewModelContext.viewState.timelineViewState.scrollToBottomPublisher)
|
scrollToBottomPublisher: viewModelContext.viewState.timelineState.scrollToBottomPublisher)
|
||||||
// Needs to be dispatched on main asynchronously otherwise we get a runtime warning
|
// Needs to be dispatched on main asynchronously otherwise we get a runtime warning
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
viewModelContext.send(viewAction: .setOpenURLAction(openURL))
|
viewModelContext.send(viewAction: .setOpenURLAction(openURL))
|
||||||
@ -44,21 +44,21 @@ struct TimelineView: UIViewControllerRepresentable {
|
|||||||
|
|
||||||
/// Updates the specified table view's properties from the current view state.
|
/// Updates the specified table view's properties from the current view state.
|
||||||
func update(tableViewController: TimelineTableViewController) {
|
func update(tableViewController: TimelineTableViewController) {
|
||||||
if tableViewController.isSwitchingTimelines != context.viewState.timelineViewState.isSwitchingTimelines {
|
if tableViewController.isSwitchingTimelines != context.viewState.timelineState.isSwitchingTimelines {
|
||||||
// Must come before timelineItemsDictionary in order to disable animations.
|
// Must come before timelineItemsDictionary in order to disable animations.
|
||||||
tableViewController.isSwitchingTimelines = context.viewState.timelineViewState.isSwitchingTimelines
|
tableViewController.isSwitchingTimelines = context.viewState.timelineState.isSwitchingTimelines
|
||||||
}
|
}
|
||||||
if tableViewController.timelineItemsDictionary != context.viewState.timelineViewState.itemsDictionary {
|
if tableViewController.timelineItemsDictionary != context.viewState.timelineState.itemsDictionary {
|
||||||
tableViewController.timelineItemsDictionary = context.viewState.timelineViewState.itemsDictionary
|
tableViewController.timelineItemsDictionary = context.viewState.timelineState.itemsDictionary
|
||||||
}
|
}
|
||||||
if tableViewController.paginationState != context.viewState.timelineViewState.paginationState {
|
if tableViewController.paginationState != context.viewState.timelineState.paginationState {
|
||||||
tableViewController.paginationState = context.viewState.timelineViewState.paginationState
|
tableViewController.paginationState = context.viewState.timelineState.paginationState
|
||||||
}
|
}
|
||||||
if tableViewController.isLive != context.viewState.timelineViewState.isLive {
|
if tableViewController.isLive != context.viewState.timelineState.isLive {
|
||||||
tableViewController.isLive = context.viewState.timelineViewState.isLive
|
tableViewController.isLive = context.viewState.timelineState.isLive
|
||||||
}
|
}
|
||||||
if tableViewController.focussedEvent != context.viewState.timelineViewState.focussedEvent {
|
if tableViewController.focussedEvent != context.viewState.timelineState.focussedEvent {
|
||||||
tableViewController.focussedEvent = context.viewState.timelineViewState.focussedEvent
|
tableViewController.focussedEvent = context.viewState.timelineState.focussedEvent
|
||||||
}
|
}
|
||||||
if tableViewController.hideTimelineMedia != context.viewState.hideTimelineMedia {
|
if tableViewController.hideTimelineMedia != context.viewState.hideTimelineMedia {
|
||||||
tableViewController.hideTimelineMedia = context.viewState.hideTimelineMedia
|
tableViewController.hideTimelineMedia = context.viewState.hideTimelineMedia
|
||||||
|
@ -43,8 +43,7 @@ struct UserProfileScreen: View {
|
|||||||
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
||||||
isVerified: context.viewState.showVerifiedBadge,
|
isVerified: context.viewState.showVerifiedBadge,
|
||||||
avatarSize: .user(on: .memberDetails),
|
avatarSize: .user(on: .memberDetails),
|
||||||
mediaProvider: context.mediaProvider,
|
mediaProvider: context.mediaProvider) { }
|
||||||
footer: { })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,7 +506,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !roomSummaryProvider.statePublisher.value.isLoaded {
|
if !roomSummaryProvider.statePublisher.value.isLoaded {
|
||||||
_ = await roomSummaryProvider.statePublisher.values.first(where: { $0.isLoaded })
|
_ = await roomSummaryProvider.statePublisher.values.first { $0.isLoaded }
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldAwait {
|
if shouldAwait {
|
||||||
|
@ -298,11 +298,11 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
.infoPublisher
|
.infoPublisher
|
||||||
.compactMap { ($0.hasRoomCall, $0.activeRoomCallParticipants) }
|
.compactMap { ($0.hasRoomCall, $0.activeRoomCallParticipants) }
|
||||||
.removeDuplicates { $0 == $1 }
|
.removeDuplicates { $0 == $1 }
|
||||||
.drop(while: { hasRoomCall, _ in
|
.drop { hasRoomCall, _ in
|
||||||
// Filter all updates before hasRoomCall becomes `true`. Then we can correctly
|
// Filter all updates before hasRoomCall becomes `true`. Then we can correctly
|
||||||
// detect its change to `false` to stop ringing when the caller hangs up.
|
// detect its change to `false` to stop ringing when the caller hangs up.
|
||||||
!hasRoomCall
|
!hasRoomCall
|
||||||
})
|
}
|
||||||
.sink { [weak self] hasOngoingCall, activeRoomCallParticipants in
|
.sink { [weak self] hasOngoingCall, activeRoomCallParticipants in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol {
|
|||||||
// as in this case no API call is made by the RustSDK and the push rules are therefore not updated.
|
// as in this case no API call is made by the RustSDK and the push rules are therefore not updated.
|
||||||
_ = await callbacks
|
_ = await callbacks
|
||||||
.timeout(.seconds(2.0), scheduler: DispatchQueue.main, options: nil, customError: nil)
|
.timeout(.seconds(2.0), scheduler: DispatchQueue.main, options: nil, customError: nil)
|
||||||
.values.first(where: { $0 == .settingsDidChange })
|
.values.first { $0 == .settingsDidChange }
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
@ -166,6 +166,19 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType]) async -> Result<any TimelineProxyProtocol, RoomProxyError> {
|
||||||
|
do {
|
||||||
|
let timeline = try await TimelineProxy(timeline: room.messageFilteredTimeline(internalIdPrefix: nil, allowedMessageTypes: allowedMessageTypes),
|
||||||
|
kind: .media)
|
||||||
|
await timeline.subscribeForUpdates()
|
||||||
|
|
||||||
|
return .success(timeline)
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed retrieving media events timeline with error: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> {
|
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> {
|
||||||
do {
|
do {
|
||||||
try await room.redact(eventId: eventID, reason: nil)
|
try await room.redact(eventId: eventID, reason: nil)
|
||||||
|
@ -70,6 +70,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 messageFilteredTimeline(allowedMessageTypes: [RoomMessageEventMessageType]) async -> Result<TimelineProxyProtocol, RoomProxyError>
|
||||||
|
|
||||||
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
|
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
|
||||||
|
|
||||||
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
|
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
|
||||||
|
@ -23,6 +23,12 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||||
|
|
||||||
|
var paginationState: PaginationState = .initial {
|
||||||
|
didSet {
|
||||||
|
callbacks.send(.paginationState(paginationState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default
|
var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default
|
||||||
var timelineItemsTimestamp: [TimelineItemIdentifier: Date] = [:]
|
var timelineItemsTimestamp: [TimelineItemIdentifier: Date] = [:]
|
||||||
|
|
||||||
@ -30,9 +36,18 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
init(timelineKind: TimelineKind = .live, listenForSignals: Bool = false) {
|
init(timelineKind: TimelineKind = .live, listenForSignals: Bool = false) {
|
||||||
self.timelineKind = timelineKind
|
self.timelineKind = timelineKind
|
||||||
callbacks.send(.paginationState(PaginationState(backward: .idle, forward: .timelineEndReached)))
|
paginationState = PaginationState(backward: .idle, forward: .timelineEndReached)
|
||||||
callbacks.send(.isLive(true))
|
callbacks.send(.isLive(true))
|
||||||
|
|
||||||
|
switch timelineKind {
|
||||||
|
case .media:
|
||||||
|
timelineItems = (0..<5).reduce([]) { partialResult, _ in
|
||||||
|
partialResult + RoomTimelineItemFixtures.mediaChunk
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
guard listenForSignals else { return }
|
guard listenForSignals else { return }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@ -56,7 +71,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||||
callbacks.send(.paginationState(PaginationState(backward: .paginating, forward: .timelineEndReached)))
|
paginationState = PaginationState(backward: .paginating, forward: .timelineEndReached)
|
||||||
|
|
||||||
if client == nil {
|
if client == nil {
|
||||||
try? await simulateBackPagination()
|
try? await simulateBackPagination()
|
||||||
@ -170,8 +185,8 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
/// Prepends the next chunk of items to the `timelineItems` array.
|
/// Prepends the next chunk of items to the `timelineItems` array.
|
||||||
private func simulateBackPagination() async throws {
|
private func simulateBackPagination() async throws {
|
||||||
defer {
|
defer {
|
||||||
callbacks.send(.paginationState(PaginationState(backward: backPaginationResponses.isEmpty ? .timelineEndReached : .idle,
|
paginationState = PaginationState(backward: backPaginationResponses.isEmpty ? .timelineEndReached : .idle,
|
||||||
forward: .timelineEndReached)))
|
forward: .timelineEndReached)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard !backPaginationResponses.isEmpty else { return }
|
guard !backPaginationResponses.isEmpty else { return }
|
||||||
|
@ -31,6 +31,12 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
||||||
|
|
||||||
|
private(set) var paginationState: PaginationState = .initial {
|
||||||
|
didSet {
|
||||||
|
callbacks.send(.paginationState(paginationState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var roomID: String {
|
var roomID: String {
|
||||||
roomProxy.id
|
roomProxy.id
|
||||||
}
|
}
|
||||||
@ -64,7 +70,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
callbacks.send(.paginationState(PaginationState(backward: .paginating, forward: .paginating)))
|
paginationState = PaginationState(backward: .paginating, forward: .paginating)
|
||||||
|
|
||||||
switch await focusOnEvent(initialFocussedEventID, timelineSize: 100) {
|
switch await focusOnEvent(initialFocussedEventID, timelineSize: 100) {
|
||||||
case .success:
|
case .success:
|
||||||
break
|
break
|
||||||
@ -368,7 +375,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
isSwitchingTimelines = true
|
isSwitchingTimelines = true
|
||||||
|
|
||||||
// Inform the world that the initial items are loading from the store
|
// Inform the world that the initial items are loading from the store
|
||||||
callbacks.send(.paginationState(.init(backward: .paginating, forward: .paginating)))
|
paginationState = PaginationState(backward: .paginating, forward: .paginating)
|
||||||
callbacks.send(.isLive(activeTimelineProvider.kind == .live))
|
callbacks.send(.isLive(activeTimelineProvider.kind == .live))
|
||||||
|
|
||||||
updateTimelineItemsCancellable = activeTimelineProvider
|
updateTimelineItemsCancellable = activeTimelineProvider
|
||||||
@ -446,7 +453,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
callbacks.send(.updatedTimelineItems(timelineItems: newTimelineItems, isSwitchingTimelines: isNewTimeline))
|
callbacks.send(.updatedTimelineItems(timelineItems: newTimelineItems, isSwitchingTimelines: isNewTimeline))
|
||||||
callbacks.send(.paginationState(paginationState))
|
self.paginationState = paginationState
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildTimelineItem(for itemProxy: TimelineItemProxy) -> RoomTimelineItemProtocol? {
|
private func buildTimelineItem(for itemProxy: TimelineItemProxy) -> RoomTimelineItemProtocol? {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
||||||
func buildRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
func buildRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||||
@ -20,12 +21,13 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
|||||||
appSettings: ServiceLocator.shared.settings)
|
appSettings: ServiceLocator.shared.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildRoomPinnedTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
func buildPinnedEventsRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol? {
|
mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol? {
|
||||||
guard let pinnedEventsTimeline = await roomProxy.pinnedEventsTimeline else {
|
guard let pinnedEventsTimeline = await roomProxy.pinnedEventsTimeline else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return RoomTimelineController(roomProxy: roomProxy,
|
return RoomTimelineController(roomProxy: roomProxy,
|
||||||
timelineProxy: pinnedEventsTimeline,
|
timelineProxy: pinnedEventsTimeline,
|
||||||
initialFocussedEventID: nil,
|
initialFocussedEventID: nil,
|
||||||
@ -33,4 +35,21 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
|||||||
mediaProvider: mediaProvider,
|
mediaProvider: mediaProvider,
|
||||||
appSettings: ServiceLocator.shared.settings)
|
appSettings: ServiceLocator.shared.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildMessageFilteredRoomTimelineController(allowedMessageTypes: [RoomMessageEventMessageType],
|
||||||
|
roomProxy: JoinedRoomProxyProtocol,
|
||||||
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
|
mediaProvider: MediaProviderProtocol) async -> Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError> {
|
||||||
|
switch await roomProxy.messageFilteredTimeline(allowedMessageTypes: allowedMessageTypes) {
|
||||||
|
case .success(let timelineProxy):
|
||||||
|
return .success(RoomTimelineController(roomProxy: roomProxy,
|
||||||
|
timelineProxy: timelineProxy,
|
||||||
|
initialFocussedEventID: nil,
|
||||||
|
timelineItemFactory: timelineItemFactory,
|
||||||
|
mediaProvider: mediaProvider,
|
||||||
|
appSettings: ServiceLocator.shared.settings))
|
||||||
|
case .failure(let error):
|
||||||
|
return .failure(.roomProxyError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
enum RoomTimelineFactoryControllerError: Error {
|
||||||
|
case roomProxyError(RoomProxyError)
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol RoomTimelineControllerFactoryProtocol {
|
protocol RoomTimelineControllerFactoryProtocol {
|
||||||
@ -13,9 +18,15 @@ protocol RoomTimelineControllerFactoryProtocol {
|
|||||||
initialFocussedEventID: String?,
|
initialFocussedEventID: String?,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol
|
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol
|
||||||
func buildRoomPinnedTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
|
||||||
|
func buildPinnedEventsRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol?
|
mediaProvider: MediaProviderProtocol) async -> RoomTimelineControllerProtocol?
|
||||||
|
|
||||||
|
func buildMessageFilteredRoomTimelineController(allowedMessageTypes: [RoomMessageEventMessageType],
|
||||||
|
roomProxy: JoinedRoomProxyProtocol,
|
||||||
|
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||||
|
mediaProvider: MediaProviderProtocol) async -> Result<RoomTimelineControllerProtocol, RoomTimelineFactoryControllerError>
|
||||||
}
|
}
|
||||||
|
|
||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
|
@ -31,7 +31,12 @@ protocol RoomTimelineControllerProtocol {
|
|||||||
var roomID: String { get }
|
var roomID: String { get }
|
||||||
var timelineKind: TimelineKind { get }
|
var timelineKind: TimelineKind { get }
|
||||||
|
|
||||||
|
/// The currently known items, use only for setting up the intial state.
|
||||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||||
|
|
||||||
|
/// The current pagination state, use only for setting up the intial state
|
||||||
|
var paginationState: PaginationState { get }
|
||||||
|
|
||||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||||
|
|
||||||
func processItemAppearance(_ itemID: TimelineItemIdentifier) async
|
func processItemAppearance(_ itemID: TimelineItemIdentifier) async
|
||||||
|
@ -42,7 +42,7 @@ extension AggregatedReaction {
|
|||||||
|
|
||||||
/// Whether to highlight the reaction, indicating that the current user sent this reaction.
|
/// Whether to highlight the reaction, indicating that the current user sent this reaction.
|
||||||
var isHighlighted: Bool {
|
var isHighlighted: Bool {
|
||||||
senders.contains(where: { $0.id == accountOwnerID })
|
senders.contains { $0.id == accountOwnerID }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The key to be displayed on screen. See `maxDisplayChars`.
|
/// The key to be displayed on screen. See `maxDisplayChars`.
|
||||||
|
@ -13,7 +13,7 @@ import MatrixRustSDK
|
|||||||
/// Its value is consistent only per timeline instance, it should **not** be used to identify an item across timeline instances.
|
/// Its value is consistent only per timeline instance, it should **not** be used to identify an item across timeline instances.
|
||||||
/// - eventOrTransactionID: Contains the 2 possible identifiers of an event, either it has a remote event id or
|
/// - eventOrTransactionID: Contains the 2 possible identifiers of an event, either it has a remote event id or
|
||||||
/// a local transaction id, never both or none.
|
/// a local transaction id, never both or none.
|
||||||
enum TimelineItemIdentifier: Hashable {
|
enum TimelineItemIdentifier: Hashable, Sendable {
|
||||||
case event(uniqueID: TimelineUniqueId, eventOrTransactionID: EventOrTransactionId)
|
case event(uniqueID: TimelineUniqueId, eventOrTransactionID: EventOrTransactionId)
|
||||||
case virtual(uniqueID: TimelineUniqueId)
|
case virtual(uniqueID: TimelineUniqueId)
|
||||||
|
|
||||||
|
@ -61,8 +61,9 @@ struct VoiceMessageRoomPlaybackView: View {
|
|||||||
if let url = playerState.fileURL {
|
if let url = playerState.fileURL {
|
||||||
WaveformView(audioURL: url,
|
WaveformView(audioURL: url,
|
||||||
configuration: .init(style: .striped(.init(color: .black, width: waveformLineWidth, spacing: waveformLinePadding)),
|
configuration: .init(style: .striped(.init(color: .black, width: waveformLineWidth, spacing: waveformLinePadding)),
|
||||||
verticalScalingFactor: 1.0),
|
verticalScalingFactor: 1.0)) {
|
||||||
placeholder: { estimatedWaveformView })
|
estimatedWaveformView
|
||||||
|
}
|
||||||
.progressMask(progress: playerState.progress)
|
.progressMask(progress: playerState.progress)
|
||||||
} else {
|
} else {
|
||||||
estimatedWaveformView
|
estimatedWaveformView
|
||||||
|
@ -92,7 +92,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:
|
case .detached, .media:
|
||||||
return await focussedPaginate(.backwards, requestSize: requestSize)
|
return await focussedPaginate(.backwards, requestSize: requestSize)
|
||||||
case .pinned:
|
case .pinned:
|
||||||
return .success(())
|
return .success(())
|
||||||
@ -319,7 +319,6 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
return .success(())
|
return .success(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next function_parameter_count
|
|
||||||
func sendVideo(url: URL,
|
func sendVideo(url: URL,
|
||||||
thumbnailURL: URL,
|
thumbnailURL: URL,
|
||||||
videoInfo: VideoInfo,
|
videoInfo: VideoInfo,
|
||||||
@ -580,7 +579,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, .media:
|
||||||
// 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 {
|
|||||||
case live
|
case live
|
||||||
case detached
|
case detached
|
||||||
case pinned
|
case pinned
|
||||||
|
case media
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TimelineProxyError: Error {
|
enum TimelineProxyError: Error {
|
||||||
|
@ -232,7 +232,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
|
|||||||
|
|
||||||
private func finalizeRecording() async -> Result<Void, VoiceMessageRecorderError> {
|
private func finalizeRecording() async -> Result<Void, VoiceMessageRecorderError> {
|
||||||
MXLog.info("finalize audio recording")
|
MXLog.info("finalize audio recording")
|
||||||
guard let url = audioRecorder.audioFileURL, audioRecorder.currentTime > 0 else {
|
guard audioRecorder.audioFileURL != nil, audioRecorder.currentTime > 0 else {
|
||||||
return .failure(.previewNotAvailable)
|
return .failure(.previewNotAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +377,12 @@ extension PreviewTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test_mediaEventsTimelineScreen() {
|
||||||
|
for preview in MediaEventsTimelineScreen_Previews._allPreviews {
|
||||||
|
assertSnapshots(matching: preview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func test_mediaUploadPreviewScreen() {
|
func test_mediaUploadPreviewScreen() {
|
||||||
for preview in MediaUploadPreviewScreen_Previews._allPreviews {
|
for preview in MediaUploadPreviewScreen_Previews._allPreviews {
|
||||||
assertSnapshots(matching: preview)
|
assertSnapshots(matching: preview)
|
||||||
|
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-en-GB.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-en-GB.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-en-GB.2.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-en-GB.2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-pseudo.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-pseudo.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-pseudo.2.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-pseudo.2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-en-GB.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-en-GB.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-en-GB.2.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-en-GB.2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-pseudo.1.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-pseudo.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-pseudo.2.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-pseudo.2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -79,7 +79,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(attributedString.runs.count, 3)
|
XCTAssertEqual(attributedString.runs.count, 3)
|
||||||
|
|
||||||
let link = attributedString.runs.first(where: { $0.link != nil })?.link
|
let link = attributedString.runs.first { $0.link != nil }?.link
|
||||||
|
|
||||||
XCTAssertEqual(link?.host, "www.matrix.org")
|
XCTAssertEqual(link?.host, "www.matrix.org")
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(attributedString.runs.count, 3)
|
XCTAssertEqual(attributedString.runs.count, 3)
|
||||||
|
|
||||||
let link = attributedString.runs.first(where: { $0.link != nil })?.link
|
let link = attributedString.runs.first { $0.link != nil }?.link
|
||||||
|
|
||||||
XCTAssertEqual(link?.host, "www.matrix.org")
|
XCTAssertEqual(link?.host, "www.matrix.org")
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(attributedString.runs.count, 3)
|
XCTAssertEqual(attributedString.runs.count, 3)
|
||||||
|
|
||||||
let link = attributedString.runs.first(where: { $0.link != nil })?.link
|
let link = attributedString.runs.first { $0.link != nil }?.link
|
||||||
|
|
||||||
XCTAssertEqual(link?.host, "www.matrix.org")
|
XCTAssertEqual(link?.host, "www.matrix.org")
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(attributedString.runs.count, 3)
|
XCTAssertEqual(attributedString.runs.count, 3)
|
||||||
|
|
||||||
let link = attributedString.runs.first(where: { $0.link != nil })?.link
|
let link = attributedString.runs.first { $0.link != nil }?.link
|
||||||
|
|
||||||
XCTAssertEqual(link, "https://matrix.org")
|
XCTAssertEqual(link, "https://matrix.org")
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class BlockedUsersScreenViewModelTests: XCTestCase {
|
|||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||||
|
|
||||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.blockedUsers.contains(where: { $0.displayName != nil }) }
|
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.blockedUsers.contains { $0.displayName != nil } }
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
|
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
|
||||||
@ -35,7 +35,7 @@ class BlockedUsersScreenViewModelTests: XCTestCase {
|
|||||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.context.$viewState) { $0.blockedUsers.contains(where: { $0.displayName != nil }) }
|
let deferred = deferFulfillment(viewModel.context.$viewState) { $0.blockedUsers.contains { $0.displayName != nil } }
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
|
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
|
||||||
|
@ -54,10 +54,10 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
.map(\.composerMode)
|
.map(\.composerMode)
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.dropFirst()
|
.dropFirst()
|
||||||
.sink(receiveValue: { composerMode in
|
.sink { composerMode in
|
||||||
XCTAssertEqual(composerMode, mode)
|
XCTAssertEqual(composerMode, mode)
|
||||||
expectation.fulfill()
|
expectation.fulfill()
|
||||||
})
|
}
|
||||||
|
|
||||||
viewModel.process(timelineAction: .setMode(mode: mode))
|
viewModel.process(timelineAction: .setMode(mode: mode))
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class JoinRoomScreenViewModelTests: XCTestCase {
|
|||||||
topic: nil,
|
topic: nil,
|
||||||
avatarURL: nil,
|
avatarURL: nil,
|
||||||
memberCount: 0,
|
memberCount: 0,
|
||||||
isHistoryWorldReadable: false,
|
isHistoryWorldReadable: nil,
|
||||||
isJoined: false,
|
isJoined: false,
|
||||||
isInvited: false,
|
isInvited: false,
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
|
@ -34,7 +34,7 @@ class MessageForwardingScreenViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testInitialState() {
|
func testInitialState() {
|
||||||
XCTAssertNil(context.viewState.rooms.first(where: { $0.id == forwardingItem.roomID }), "The source room ID shouldn't be shown")
|
XCTAssertNil(context.viewState.rooms.first { $0.id == forwardingItem.roomID }, "The source room ID shouldn't be shown")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRoomSelection() {
|
func testRoomSelection() {
|
||||||
|
@ -29,7 +29,7 @@ class PollFormScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertFalse(context.viewState.bindings.isUndisclosed)
|
XCTAssertFalse(context.viewState.bindings.isUndisclosed)
|
||||||
|
|
||||||
// Cancellation should work without confirmation
|
// Cancellation should work without confirmation
|
||||||
let deferred = deferFulfillment(viewModel.actions, until: { _ in true })
|
let deferred = deferFulfillment(viewModel.actions) { _ in true }
|
||||||
context.send(viewAction: .cancel)
|
context.send(viewAction: .cancel)
|
||||||
let action = try await deferred.fulfill()
|
let action = try await deferred.fulfill()
|
||||||
XCTAssertNil(context.alertInfo)
|
XCTAssertNil(context.alertInfo)
|
||||||
@ -45,7 +45,7 @@ class PollFormScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertFalse(context.viewState.bindings.isUndisclosed)
|
XCTAssertFalse(context.viewState.bindings.isUndisclosed)
|
||||||
|
|
||||||
// Cancellation should work without confirmation
|
// Cancellation should work without confirmation
|
||||||
let deferred = deferFulfillment(viewModel.actions, until: { _ in true })
|
let deferred = deferFulfillment(viewModel.actions) { _ in true }
|
||||||
context.send(viewAction: .cancel)
|
context.send(viewAction: .cancel)
|
||||||
let action = try await deferred.fulfill()
|
let action = try await deferred.fulfill()
|
||||||
XCTAssertNil(context.alertInfo)
|
XCTAssertNil(context.alertInfo)
|
||||||
|
@ -161,8 +161,8 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
// Then no warning should be shown, and the call to update the users should be made straight away.
|
// Then no warning should be shown, and the call to update the users should be made straight away.
|
||||||
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
||||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.count, 2)
|
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.count, 2)
|
||||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains(where: { $0.userID == existingModerator.id && $0.powerLevel == 0 }), true)
|
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains { $0.userID == existingModerator.id && $0.powerLevel == 0 }, true)
|
||||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains(where: { $0.userID == firstUser.id && $0.powerLevel == 50 }), true)
|
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains { $0.userID == firstUser.id && $0.powerLevel == 50 }, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSavePromotedAdministrator() async throws {
|
func testSavePromotedAdministrator() async throws {
|
||||||
@ -189,7 +189,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
|
|||||||
// Then the user should be made into an administrator.
|
// Then the user should be made into an administrator.
|
||||||
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
XCTAssertTrue(roomProxy.updatePowerLevelsForUsersCalled)
|
||||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.count, 1)
|
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.count, 1)
|
||||||
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains(where: { $0.userID == firstUser.id && $0.powerLevel == 100 }), true)
|
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains { $0.userID == firstUser.id && $0.powerLevel == 100 }, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupViewModel(mode: RoomMemberDetails.Role) {
|
private func setupViewModel(mode: RoomMemberDetails.Role) {
|
||||||
|
@ -322,7 +322,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
|||||||
topic: nil,
|
topic: nil,
|
||||||
avatarURL: nil,
|
avatarURL: nil,
|
||||||
memberCount: 0,
|
memberCount: 0,
|
||||||
isHistoryWorldReadable: false,
|
isHistoryWorldReadable: nil,
|
||||||
isJoined: false,
|
isJoined: false,
|
||||||
isInvited: true,
|
isInvited: true,
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
|
@ -49,7 +49,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
|
|||||||
context.send(viewAction: .editOwnUserRole)
|
context.send(viewAction: .editOwnUserRole)
|
||||||
XCTAssertNotNil(context.alertInfo)
|
XCTAssertNotNil(context.alertInfo)
|
||||||
|
|
||||||
context.alertInfo?.verticalButtons?.first(where: { $0.title.localizedStandardContains("moderator") })?.action?()
|
context.alertInfo?.verticalButtons?.first { $0.title.localizedStandardContains("moderator") }?.action?()
|
||||||
|
|
||||||
try await Task.sleep(for: .milliseconds(100))
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
|
|||||||
context.send(viewAction: .editOwnUserRole)
|
context.send(viewAction: .editOwnUserRole)
|
||||||
XCTAssertNotNil(context.alertInfo)
|
XCTAssertNotNil(context.alertInfo)
|
||||||
|
|
||||||
context.alertInfo?.verticalButtons?.first(where: { $0.title.localizedStandardContains("member") })?.action?()
|
context.alertInfo?.verticalButtons?.first { $0.title.localizedStandardContains("member") }?.action?()
|
||||||
|
|
||||||
try await Task.sleep(for: .milliseconds(100))
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
|
@ -45,9 +45,9 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
// Then the messages should be grouped together.
|
// Then the messages should be grouped together.
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the middle message from being grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the middle message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageGroupingMultipleSenders() {
|
func testMessageGroupingMultipleSenders() {
|
||||||
@ -73,12 +73,12 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
// Then the messages should be grouped by sender.
|
// Then the messages should be grouped by sender.
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[3].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[3].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[4].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[4].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[5].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[5].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageGroupingWithLeadingReactions() {
|
func testMessageGroupingWithLeadingReactions() {
|
||||||
@ -99,9 +99,9 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
// Then the first message should not be grouped but the other two should.
|
// Then the first message should not be grouped but the other two should.
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .single, "When the first message has reactions it should not be grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .single, "When the first message has reactions it should not be grouped.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .first, "A new group should be made when the preceding message has reactions.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .first, "A new group should be made when the preceding message has reactions.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageGroupingWithInnerReactions() {
|
func testMessageGroupingWithInnerReactions() {
|
||||||
@ -122,9 +122,9 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
// Then the first and second messages should be grouped and the last one should not.
|
// Then the first and second messages should be grouped and the last one should not.
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .last, "When the message has reactions, the group should end here.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .last, "When the message has reactions, the group should end here.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .single, "The last message should not be grouped when the preceding message has reactions.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .single, "The last message should not be grouped when the preceding message has reactions.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageGroupingWithTrailingReactions() {
|
func testMessageGroupingWithTrailingReactions() {
|
||||||
@ -145,9 +145,9 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
// Then the messages should be grouped together.
|
// Then the messages should be grouped together.
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the second message from being grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the second message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.timelineViewState.itemViewStates[2].groupStyle, .last, "Reactions on the last message should not prevent it from being grouped.")
|
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Reactions on the last message should not prevent it from being grouped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Focussing
|
// MARK: - Focussing
|
||||||
@ -162,18 +162,18 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEvent)
|
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||||
|
|
||||||
// When focussing on an item that isn't loaded.
|
// When focussing on an item that isn't loaded.
|
||||||
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineViewState.isLive }
|
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineState.isLive }
|
||||||
await viewModel.focusOnEvent(eventID: "t4")
|
await viewModel.focusOnEvent(eventID: "t4")
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then a new timeline should be loaded and the room focussed on that event.
|
// Then a new timeline should be loaded and the room focussed on that event.
|
||||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 1)
|
XCTAssertEqual(timelineController.focusOnEventCallCount, 1)
|
||||||
XCTAssertFalse(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertFalse(viewModel.context.viewState.timelineState.isLive)
|
||||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFocusLoadedItem() async throws {
|
func testFocusLoadedItem() async throws {
|
||||||
@ -186,18 +186,18 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEvent)
|
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||||
|
|
||||||
// When focussing on a loaded item.
|
// When focussing on a loaded item.
|
||||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { !$0.timelineViewState.isLive }
|
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { !$0.timelineState.isLive }
|
||||||
await viewModel.focusOnEvent(eventID: "t1")
|
await viewModel.focusOnEvent(eventID: "t1")
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the timeline should remain live and the item should be focussed.
|
// Then the timeline should remain live and the item should be focussed.
|
||||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEvent, .init(eventID: "t1", appearance: .animated))
|
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t1", appearance: .animated))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFocusLive() async throws {
|
func testFocusLive() async throws {
|
||||||
@ -210,30 +210,30 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
let viewModel = makeViewModel(timelineController: timelineController)
|
let viewModel = makeViewModel(timelineController: timelineController)
|
||||||
|
|
||||||
var deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineViewState.isLive }
|
var deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineState.isLive }
|
||||||
await viewModel.focusOnEvent(eventID: "t4")
|
await viewModel.focusOnEvent(eventID: "t4")
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
XCTAssertEqual(timelineController.focusLiveCallCount, 0)
|
XCTAssertEqual(timelineController.focusLiveCallCount, 0)
|
||||||
XCTAssertFalse(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertFalse(viewModel.context.viewState.timelineState.isLive)
|
||||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
||||||
|
|
||||||
// When switching back to a live timeline.
|
// When switching back to a live timeline.
|
||||||
deferred = deferFulfillment(viewModel.context.$viewState) { $0.timelineViewState.isLive }
|
deferred = deferFulfillment(viewModel.context.$viewState) { $0.timelineState.isLive }
|
||||||
viewModel.context.send(viewAction: .focusLive)
|
viewModel.context.send(viewAction: .focusLive)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the timeline should switch back to being live and the event focus should be removed.
|
// Then the timeline should switch back to being live and the event focus should be removed.
|
||||||
XCTAssertEqual(timelineController.focusLiveCallCount, 1)
|
XCTAssertEqual(timelineController.focusLiveCallCount, 1)
|
||||||
XCTAssertTrue(viewModel.context.viewState.timelineViewState.isLive)
|
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||||
XCTAssertNil(viewModel.context.viewState.timelineViewState.focussedEvent)
|
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInitialFocusViewState() async throws {
|
func testInitialFocusViewState() async throws {
|
||||||
let timelineController = MockRoomTimelineController()
|
let timelineController = MockRoomTimelineController()
|
||||||
|
|
||||||
let viewModel = makeViewModel(focussedEventID: "t10", timelineController: timelineController)
|
let viewModel = makeViewModel(focussedEventID: "t10", timelineController: timelineController)
|
||||||
XCTAssertEqual(viewModel.context.viewState.timelineViewState.focussedEvent, .init(eventID: "t10", appearance: .immediate))
|
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t10", appearance: .immediate))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Read Receipts
|
// MARK: - Read Receipts
|
||||||
|
Loading…
x
Reference in New Issue
Block a user