Bump the RustSDK to v1.0.53: adopt latest record based timeline item APIs (#3356)

This commit is contained in:
Stefan Ceriu
2024-10-01 18:50:11 +03:00
committed by GitHub
parent 268d9f7479
commit c79dbe85a0
49 changed files with 776 additions and 2172 deletions
+5 -9
View File
@@ -137,7 +137,6 @@
1B8E30B35BF8F541C1318F19 /* SecureBackupScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */; }; 1B8E30B35BF8F541C1318F19 /* SecureBackupScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */; };
1BA04D05EBC6646958B1BE60 /* PlaceholderScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */; }; 1BA04D05EBC6646958B1BE60 /* PlaceholderScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */; };
1C409A26A99F0371C47AFA51 /* UserDiscoveryServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F615A00DB223FF3280204D2 /* UserDiscoveryServiceProtocol.swift */; }; 1C409A26A99F0371C47AFA51 /* UserDiscoveryServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F615A00DB223FF3280204D2 /* UserDiscoveryServiceProtocol.swift */; };
1C815DD79B401DEBA2914773 /* TimelineItemMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4A1003A0F7A1DFB47F4E2D0 /* TimelineItemMock.swift */; };
1C8BC70A18060677E295A846 /* ShareToMapsAppActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */; }; 1C8BC70A18060677E295A846 /* ShareToMapsAppActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */; };
1C9BB74711E5F24C77B7FED0 /* RoomMembersListScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */; }; 1C9BB74711E5F24C77B7FED0 /* RoomMembersListScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */; };
1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; }; 1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; };
@@ -211,6 +210,7 @@
2DD9D0FE7CB5CFC80D071451 /* AppLockScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */; }; 2DD9D0FE7CB5CFC80D071451 /* AppLockScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */; };
2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */; }; 2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */; };
2E8C6672D0EE7D5B1BEDB8E2 /* ServerConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7478623CECC9438014244BA /* ServerConfirmationScreen.swift */; }; 2E8C6672D0EE7D5B1BEDB8E2 /* ServerConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7478623CECC9438014244BA /* ServerConfirmationScreen.swift */; };
2F09DF0CB213CAE86A3E3B67 /* EventTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B10423B9102086A2D9BFCBA /* EventTimelineItem.swift */; };
2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086B997409328F091EBA43CE /* RoomScreenUITests.swift */; }; 2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086B997409328F091EBA43CE /* RoomScreenUITests.swift */; };
2F6207CB5C4715FE313B1E95 /* TimelineViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6509708F54FC883604DFDC95 /* TimelineViewModelTests.swift */; }; 2F6207CB5C4715FE313B1E95 /* TimelineViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6509708F54FC883604DFDC95 /* TimelineViewModelTests.swift */; };
2F623DA1122140A987B34D08 /* NotificationSettingsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */; }; 2F623DA1122140A987B34D08 /* NotificationSettingsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */; };
@@ -407,7 +407,6 @@
5AE6404C4FD4848ACCFF9EDC /* SecureBackupLogoutConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */; }; 5AE6404C4FD4848ACCFF9EDC /* SecureBackupLogoutConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */; };
5B6E5AD224509E6C0B520D6E /* RoomMemberDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDF49CEBC0DFC59C308335F /* RoomMemberDetailsScreenViewModelProtocol.swift */; }; 5B6E5AD224509E6C0B520D6E /* RoomMemberDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDF49CEBC0DFC59C308335F /* RoomMemberDetailsScreenViewModelProtocol.swift */; };
5B7D24A318AFF75AD611A026 /* RoomDirectorySearchScreenScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE6BFF453838CF6C3982C5A3 /* RoomDirectorySearchScreenScreenViewModelTests.swift */; }; 5B7D24A318AFF75AD611A026 /* RoomDirectorySearchScreenScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE6BFF453838CF6C3982C5A3 /* RoomDirectorySearchScreenScreenViewModelTests.swift */; };
5BBDF9926CB645DE2F7BC258 /* EventTimelineItemSDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86D447D771CEF6194348F5F /* EventTimelineItemSDKMock.swift */; };
5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */; }; 5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */; };
5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; }; 5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; };
5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */; }; 5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */; };
@@ -1309,6 +1308,7 @@
1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMap.swift; sourceTree = "<group>"; }; 1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStaticMap.swift; sourceTree = "<group>"; };
1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = "<group>"; }; 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = "<group>"; };
1B065EC39C99C1303A101C1C /* WebRegistrationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRegistrationScreen.swift; sourceTree = "<group>"; }; 1B065EC39C99C1303A101C1C /* WebRegistrationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRegistrationScreen.swift; sourceTree = "<group>"; };
1B10423B9102086A2D9BFCBA /* EventTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTimelineItem.swift; sourceTree = "<group>"; };
1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItem.swift; sourceTree = "<group>"; }; 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItem.swift; sourceTree = "<group>"; };
1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillAttachmentViewProvider.swift; sourceTree = "<group>"; }; 1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillAttachmentViewProvider.swift; sourceTree = "<group>"; };
1B564D748B67A156F413CD97 /* NotificationSettingsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenModels.swift; sourceTree = "<group>"; }; 1B564D748B67A156F413CD97 /* NotificationSettingsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenModels.swift; sourceTree = "<group>"; };
@@ -1904,7 +1904,6 @@
A433BE28B40D418237BE37B5 /* ReportContentScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreen.swift; sourceTree = "<group>"; }; A433BE28B40D418237BE37B5 /* ReportContentScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreen.swift; sourceTree = "<group>"; };
A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; }; A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = "<group>"; }; A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = "<group>"; };
A4A1003A0F7A1DFB47F4E2D0 /* TimelineItemMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMock.swift; sourceTree = "<group>"; };
A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineFlowCoordinator.swift; sourceTree = "<group>"; }; A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineFlowCoordinator.swift; sourceTree = "<group>"; };
A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetectionTests.swift; sourceTree = "<group>"; }; A58E93D91DE3288010390DEE /* EmojiDetectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetectionTests.swift; sourceTree = "<group>"; };
A6702BC84D3CC2421D78CD4E /* WebRegistrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRegistrationScreenViewModel.swift; sourceTree = "<group>"; }; A6702BC84D3CC2421D78CD4E /* WebRegistrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRegistrationScreenViewModel.swift; sourceTree = "<group>"; };
@@ -2003,7 +2002,6 @@
B81B6170DB690013CEB646F4 /* MapLibreModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreModels.swift; sourceTree = "<group>"; }; B81B6170DB690013CEB646F4 /* MapLibreModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreModels.swift; sourceTree = "<group>"; };
B8516302ACCA94A0E680AB3B /* VoiceMessageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageButton.swift; sourceTree = "<group>"; }; B8516302ACCA94A0E680AB3B /* VoiceMessageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageButton.swift; sourceTree = "<group>"; };
B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = "<group>"; }; B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = "<group>"; };
B86D447D771CEF6194348F5F /* EventTimelineItemSDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTimelineItemSDKMock.swift; sourceTree = "<group>"; };
B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPicker.swift; sourceTree = "<group>"; }; B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPicker.swift; sourceTree = "<group>"; };
B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCoordinators.swift; sourceTree = "<group>"; }; B8F28602AC7AC881AED37EBA /* NavigationCoordinators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationCoordinators.swift; sourceTree = "<group>"; };
B902EA6CD3296B0E10EE432B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = "<group>"; }; B902EA6CD3296B0E10EE432B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = "<group>"; };
@@ -2899,6 +2897,7 @@
E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */, E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */,
4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */, 4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */,
E321E840DCC63790049984F4 /* ElementCallServiceMock.swift */, E321E840DCC63790049984F4 /* ElementCallServiceMock.swift */,
1B10423B9102086A2D9BFCBA /* EventTimelineItem.swift */,
867DC9530C42F7B5176BE465 /* JoinedRoomProxyMock.swift */, 867DC9530C42F7B5176BE465 /* JoinedRoomProxyMock.swift */,
8DA1E8F287680C8ED25EDBAC /* NetworkMonitorMock.swift */, 8DA1E8F287680C8ED25EDBAC /* NetworkMonitorMock.swift */,
382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */, 382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */,
@@ -2911,7 +2910,6 @@
D479DF730528153665E5782E /* RoomTimelineControllerFactoryMock.swift */, D479DF730528153665E5782E /* RoomTimelineControllerFactoryMock.swift */,
F74532E01B317C56C1BE8FA8 /* RoomTimelineProviderMock.swift */, F74532E01B317C56C1BE8FA8 /* RoomTimelineProviderMock.swift */,
248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */, 248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */,
A4A1003A0F7A1DFB47F4E2D0 /* TimelineItemMock.swift */,
7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */, 7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */,
AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */, AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */,
F4469F6AE311BDC439B3A5EC /* UserSessionMock.swift */, F4469F6AE311BDC439B3A5EC /* UserSessionMock.swift */,
@@ -5257,7 +5255,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */, 8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */,
B86D447D771CEF6194348F5F /* EventTimelineItemSDKMock.swift */,
); );
path = SDK; path = SDK;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -6449,7 +6446,7 @@
50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */, 50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */,
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */, F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */,
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */, 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */,
5BBDF9926CB645DE2F7BC258 /* EventTimelineItemSDKMock.swift in Sources */, 2F09DF0CB213CAE86A3E3B67 /* EventTimelineItem.swift in Sources */,
63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */, 63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */,
5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */, 5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */,
D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */, D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */,
@@ -6933,7 +6930,6 @@
6C98153D60FF9B648C166C27 /* TimelineItemMenu.swift in Sources */, 6C98153D60FF9B648C166C27 /* TimelineItemMenu.swift in Sources */,
AE07F215EBC2B9CBF17AA54B /* TimelineItemMenuAction.swift in Sources */, AE07F215EBC2B9CBF17AA54B /* TimelineItemMenuAction.swift in Sources */,
12CD8B5CC30A05061228BF9E /* TimelineItemMenuActionProvider.swift in Sources */, 12CD8B5CC30A05061228BF9E /* TimelineItemMenuActionProvider.swift in Sources */,
1C815DD79B401DEBA2914773 /* TimelineItemMock.swift in Sources */,
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */, 440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */, 9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */,
F3ECA377FF77E81A4F1FA062 /* TimelineItemSendInfoLabel.swift in Sources */, F3ECA377FF77E81A4F1FA062 /* TimelineItemSendInfoLabel.swift in Sources */,
@@ -7799,7 +7795,7 @@
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 1.0.52; version = 1.0.53;
}; };
}; };
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
@@ -149,8 +149,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/matrix-rust-components-swift", "location" : "https://github.com/element-hq/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "11c6d99d62035b02f4c448daebff4d63c962da20", "revision" : "83abbdc8485340c20f27148153ff62f690ca210b",
"version" : "1.0.52" "version" : "1.0.53"
} }
}, },
{ {
@@ -29,7 +29,7 @@ protocol AppMediatorProtocol {
func requestAuthorizationIfNeeded() async -> Bool func requestAuthorizationIfNeeded() async -> Bool
} }
extension UIApplication.State: CustomStringConvertible { extension UIApplication.State: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
switch self { switch self {
case .active: case .active:
@@ -44,7 +44,7 @@ extension UIApplication.State: CustomStringConvertible {
} }
} }
extension UIUserInterfaceActiveAppearance: CustomStringConvertible { extension UIUserInterfaceActiveAppearance: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
switch self { switch self {
case .active: case .active:
@@ -0,0 +1,56 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Foundation
import LoremSwiftum
import MatrixRustSDK
struct EventTimelineItemSDKMockConfiguration {
var eventID: String = UUID().uuidString
var sender = ""
var isOwn = false
var content: TimelineItemContent = .redactedMessage
}
extension EventTimelineItem {
init(configuration: EventTimelineItemSDKMockConfiguration) {
self.init(isLocal: false,
isRemote: true,
eventOrTransactionId: .eventId(eventId: configuration.eventID),
sender: configuration.sender,
senderProfile: .pending,
isOwn: configuration.isOwn,
isEditable: false,
content: configuration.content,
timestamp: 0,
reactions: [],
debugInfoProvider: EventTimelineItemDebugInfoProviderSDKMock(),
localSendState: nil,
readReceipts: [:],
origin: nil,
canBeRepliedTo: false,
shieldsProvider: EventShieldsProviderSDKMock())
}
static var mockMessage: EventTimelineItem {
let body = Lorem.sentences(Int.random(in: 1...5))
let messageType = MessageType.text(content: .init(body: body, formatted: nil))
let content = TimelineItemContent.message(content: .init(msgType: messageType,
body: body,
inReplyTo: nil,
threadRoot: nil,
isEdited: false,
mentions: nil))
return .init(configuration: .init(content: content))
}
static func mockCallInvite(sender: String) -> EventTimelineItem {
.init(configuration: .init(sender: sender, content: .callInvite))
}
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -82,7 +82,7 @@ extension Poll.Option {
extension PollRoomTimelineItem { extension PollRoomTimelineItem {
static func mock(poll: Poll, isOutgoing: Bool = true, isEditable: Bool = false) -> Self { static func mock(poll: Poll, isOutgoing: Bool = true, isEditable: Bool = false) -> Self {
.init(id: .init(timelineID: UUID().uuidString, eventID: UUID().uuidString), .init(id: .random,
poll: poll, poll: poll,
body: "poll", body: "poll",
timestamp: "Now", timestamp: "Now",
@@ -35,7 +35,12 @@ class AutoUpdatingRoomTimelineProviderMock: RoomTimelineProvider {
let diff = TimelineDiffSDKMock() let diff = TimelineDiffSDKMock()
diff.changeReturnValue = .append diff.changeReturnValue = .append
diff.appendReturnValue = [TimelineItemFixtures.messageTimelineItem]
let timelineItem = TimelineItemSDKMock()
timelineItem.asEventReturnValue = EventTimelineItem.mockMessage
timelineItem.uniqueIdReturnValue = UUID().uuidString
diff.appendReturnValue = [timelineItem]
await Self.timelineListener?.onUpdate(diff: [diff]) await Self.timelineListener?.onUpdate(diff: [diff])
} }
@@ -1,29 +0,0 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Foundation
struct EventTimelineItemSDKMockConfiguration {
var eventID: String = UUID().uuidString
}
extension EventTimelineItemSDKMock {
convenience init(configuration: EventTimelineItemSDKMockConfiguration) {
self.init()
eventIdReturnValue = configuration.eventID
isOwnReturnValue = false
timestampReturnValue = 0
isEditableReturnValue = false
canBeRepliedToReturnValue = false
senderReturnValue = ""
senderProfileReturnValue = .pending
let timelineItemContent = TimelineItemContentSDKMock()
timelineItemContent.kindReturnValue = .redactedMessage
contentReturnValue = timelineItemContent
}
}
@@ -1,66 +0,0 @@
//
// 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
import LoremSwiftum
import MatrixRustSDK
enum TimelineItemFixtures {
static var callInviteTimelineItem: TimelineItem {
let eventTimelineItem = EventTimelineItemSDKMock()
eventTimelineItem.isOwnReturnValue = true
eventTimelineItem.timestampReturnValue = 0
eventTimelineItem.isEditableReturnValue = false
eventTimelineItem.canBeRepliedToReturnValue = false
eventTimelineItem.senderReturnValue = "@bob:matrix.org"
eventTimelineItem.senderProfileReturnValue = .pending
let timelineItemContent = TimelineItemContentSDKMock()
timelineItemContent.kindReturnValue = .callInvite
eventTimelineItem.contentReturnValue = timelineItemContent
let timelineItem = TimelineItemSDKMock()
timelineItem.asEventReturnValue = eventTimelineItem
return timelineItem
}
static var messageTimelineItem: TimelineItem {
let eventTimelineItem = EventTimelineItemSDKMock()
eventTimelineItem.eventIdReturnValue = UUID().uuidString
eventTimelineItem.isOwnReturnValue = true
eventTimelineItem.timestampReturnValue = 0
eventTimelineItem.isEditableReturnValue = false
eventTimelineItem.canBeRepliedToReturnValue = false
eventTimelineItem.senderReturnValue = "@bob:matrix.org"
eventTimelineItem.senderProfileReturnValue = .pending
eventTimelineItem.reactionsReturnValue = []
eventTimelineItem.readReceiptsReturnValue = [:]
let timelineItemContent = TimelineItemContentSDKMock()
timelineItemContent.kindReturnValue = .message
let message = MessageSDKMock()
let textMessageContent = TextMessageContent(body: Lorem.sentences(Int.random(in: 1...5)), formatted: nil)
message.msgtypeReturnValue = .text(content: textMessageContent)
message.isThreadedReturnValue = false
message.isEditedReturnValue = false
timelineItemContent.asMessageReturnValue = message
eventTimelineItem.contentReturnValue = timelineItemContent
let timelineItem = TimelineItemSDKMock()
timelineItem.asEventReturnValue = eventTimelineItem
timelineItem.uniqueIdReturnValue = UUID().uuidString
return timelineItem
}
}
@@ -61,6 +61,6 @@ extension Array {
extension Array where Element == RoomTimelineItemProtocol { extension Array where Element == RoomTimelineItemProtocol {
func firstUsingStableID(_ id: TimelineItemIdentifier) -> Element? { func firstUsingStableID(_ id: TimelineItemIdentifier) -> Element? {
first { $0.id.timelineID == id.timelineID } first { $0.id.uniqueID == id.uniqueID }
} }
} }
@@ -7,7 +7,7 @@
import SwiftUI import SwiftUI
extension ProposedViewSize: Hashable { extension ProposedViewSize: @retroactive Hashable {
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(width) hasher.combine(width)
hasher.combine(height) hasher.combine(height)
+1 -1
View File
@@ -7,7 +7,7 @@
import Foundation import Foundation
extension URL: ExpressibleByStringLiteral { extension URL: @retroactive ExpressibleByStringLiteral {
public init(stringLiteral value: StaticString) { public init(stringLiteral value: StaticString) {
guard let url = URL(string: "\(value)") else { guard let url = URL(string: "\(value)") else {
fatalError("The static string used to create this URL is invalid") fatalError("The static string used to create this URL is invalid")
@@ -93,7 +93,7 @@ private struct MessageForwardingListRow: View {
struct MessageForwardingScreen_Previews: PreviewProvider, TestablePreview { struct MessageForwardingScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
let summaryProvider = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))) let summaryProvider = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))
let viewModel = MessageForwardingScreenViewModel(forwardingItem: .init(id: .init(timelineID: ""), let viewModel = MessageForwardingScreenViewModel(forwardingItem: .init(id: .init(uniqueID: ""),
roomID: "", roomID: "",
content: .init(noPointer: .init())), content: .init(noPointer: .init())),
clientProxy: ClientProxyMock(.init()), clientProxy: ClientProxyMock(.init()),
@@ -258,9 +258,9 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
case .newMessage: case .newMessage:
set(mode: .default) set(mode: .default)
case .edit(let eventID): case .edit(let eventID):
set(mode: .edit(originalItemId: .init(timelineID: "", eventID: eventID))) set(mode: .edit(originalItemId: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: eventID))))
case .reply(let eventID): case .reply(let eventID):
set(mode: .reply(itemID: .init(timelineID: "", eventID: eventID), replyDetails: .loading(eventID: eventID), isThread: false)) set(mode: .reply(itemID: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: eventID)), replyDetails: .loading(eventID: eventID), isThread: false))
replyLoadingTask = Task { replyLoadingTask = Task {
let reply = switch await draftService.getReply(eventID: eventID) { let reply = switch await draftService.getReply(eventID: eventID) {
case .success(let reply): case .success(let reply):
@@ -273,7 +273,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
return return
} }
set(mode: .reply(itemID: .init(timelineID: "", eventID: eventID), replyDetails: reply.details, isThread: reply.isThreaded)) set(mode: .reply(itemID: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: eventID)), replyDetails: reply.details, isThread: reply.isThreaded))
} }
} }
} }
@@ -415,10 +415,10 @@ extension ComposerToolbar {
mentionDisplayHelper: ComposerMentionDisplayHelper.mock, mentionDisplayHelper: ComposerMentionDisplayHelper.mock,
analyticsService: ServiceLocator.shared.analytics, analyticsService: ServiceLocator.shared.analytics,
composerDraftService: ComposerDraftServiceMock()) composerDraftService: ComposerDraftServiceMock())
model.state.composerMode = isLoading ? .reply(itemID: .init(timelineID: ""), model.state.composerMode = isLoading ? .reply(itemID: .init(uniqueID: ""),
replyDetails: .loading(eventID: ""), replyDetails: .loading(eventID: ""),
isThread: false) : isThread: false) :
.reply(itemID: .init(timelineID: ""), .reply(itemID: .init(uniqueID: ""),
replyDetails: .loaded(sender: .init(id: "", replyDetails: .loaded(sender: .init(id: "",
displayName: "Test"), displayName: "Test"),
eventID: "", eventContent: .message(.text(.init(body: "Hello World!")))), isThread: false) eventID: "", eventContent: .message(.text(.init(body: "Hello World!")))), isThread: false)
@@ -203,7 +203,7 @@ struct TimelineState {
var itemsDictionary = OrderedDictionary<String, RoomTimelineItemViewState>() var itemsDictionary = OrderedDictionary<String, RoomTimelineItemViewState>()
var timelineIDs: [String] { var uniqueIDs: [String] {
itemsDictionary.keys.elements itemsDictionary.keys.elements
} }
@@ -401,8 +401,8 @@ class TimelineTableViewController: UIViewController {
// These are already in reverse order because the table view is flipped // These are already in reverse order because the table view is flipped
for indexPath in visibleIndexPaths { for indexPath in visibleIndexPaths {
if let visibleItemTimelineID = dataSource?.itemIdentifier(for: indexPath), if let visibleItemUniqueID = dataSource?.itemIdentifier(for: indexPath),
let visibleItemID = timelineItemsDictionary[visibleItemTimelineID]?.identifier { let visibleItemID = timelineItemsDictionary[visibleItemUniqueID]?.identifier {
coordinator.send(viewAction: .sendReadReceiptIfNeeded(visibleItemID)) coordinator.send(viewAction: .sendReadReceiptIfNeeded(visibleItemID))
return return
} }
@@ -488,7 +488,7 @@ extension TimelineTableViewController {
/// The current layout of the table, based on the newest timeline item. /// The current layout of the table, based on the newest timeline item.
private func snapshotLayout() -> Layout? { private func snapshotLayout() -> Layout? {
guard let newestItemID = newestVisibleItemID(), guard let newestItemID = newestVisibleItemID(),
let newestCellFrame = cellFrame(for: newestItemID.timelineID) else { let newestCellFrame = cellFrame(for: newestItemID.uniqueID) else {
return nil return nil
} }
return Layout(id: newestItemID, frame: newestCellFrame) return Layout(id: newestItemID, frame: newestCellFrame)
@@ -496,12 +496,12 @@ extension TimelineTableViewController {
/// Restores the timeline's layout from an old snapshot. /// Restores the timeline's layout from an old snapshot.
private func restoreLayout(_ layout: Layout) { private func restoreLayout(_ layout: Layout) {
if let indexPath = dataSource?.indexPath(for: layout.id.timelineID) { if let indexPath = dataSource?.indexPath(for: layout.id.uniqueID) {
// Scroll the item into view. // Scroll the item into view.
tableView.scrollToRow(at: indexPath, at: .top, animated: false) tableView.scrollToRow(at: indexPath, at: .top, animated: false)
// Remove any unwanted offset that was added by scrollToRow. // Remove any unwanted offset that was added by scrollToRow.
if let frame = cellFrame(for: layout.id.timelineID) { if let frame = cellFrame(for: layout.id.uniqueID) {
let deltaY = frame.maxY - layout.frame.maxY let deltaY = frame.maxY - layout.frame.maxY
if deltaY != 0 { if deltaY != 0 {
tableView.contentOffset.y -= deltaY tableView.contentOffset.y -= deltaY
@@ -511,8 +511,8 @@ extension TimelineTableViewController {
} }
/// Returns the frame of the cell for a particular timeline item. /// Returns the frame of the cell for a particular timeline item.
private func cellFrame(for id: String) -> CGRect? { private func cellFrame(for uniqueID: String) -> CGRect? {
guard let timelineCell = tableView.visibleCells.first(where: { ($0 as? TimelineItemCell)?.item?.id == id }) else { guard let timelineCell = tableView.visibleCells.first(where: { ($0 as? TimelineItemCell)?.item?.identifier.uniqueID == uniqueID }) else {
return nil return nil
} }
@@ -659,19 +659,19 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
if itemGroup.count == 1 { if itemGroup.count == 1 {
if let firstItem = itemGroup.first { if let firstItem = itemGroup.first {
timelineItemsDictionary.updateValue(updateViewState(item: firstItem, groupStyle: .single), timelineItemsDictionary.updateValue(updateViewState(item: firstItem, groupStyle: .single),
forKey: firstItem.id.timelineID) forKey: firstItem.id.uniqueID)
} }
} else { } else {
for (index, item) in itemGroup.enumerated() { for (index, item) in itemGroup.enumerated() {
if index == 0 { if index == 0 {
timelineItemsDictionary.updateValue(updateViewState(item: item, groupStyle: state.isPinnedEventsTimeline ? .single : .first), timelineItemsDictionary.updateValue(updateViewState(item: item, groupStyle: state.isPinnedEventsTimeline ? .single : .first),
forKey: item.id.timelineID) forKey: item.id.uniqueID)
} else if index == itemGroup.count - 1 { } else if index == itemGroup.count - 1 {
timelineItemsDictionary.updateValue(updateViewState(item: item, groupStyle: state.isPinnedEventsTimeline ? .single : .last), timelineItemsDictionary.updateValue(updateViewState(item: item, groupStyle: state.isPinnedEventsTimeline ? .single : .last),
forKey: item.id.timelineID) forKey: item.id.uniqueID)
} else { } else {
timelineItemsDictionary.updateValue(updateViewState(item: item, groupStyle: state.isPinnedEventsTimeline ? .single : .middle), timelineItemsDictionary.updateValue(updateViewState(item: item, groupStyle: state.isPinnedEventsTimeline ? .single : .middle),
forKey: item.id.timelineID) forKey: item.id.uniqueID)
} }
} }
} }
@@ -685,7 +685,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
} }
private func updateViewState(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewState { private func updateViewState(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewState {
if let timelineItemViewState = state.timelineViewState.itemsDictionary[item.id.timelineID] { if let timelineItemViewState = state.timelineViewState.itemsDictionary[item.id.uniqueID] {
timelineItemViewState.groupStyle = groupStyle timelineItemViewState.groupStyle = groupStyle
timelineItemViewState.type = .init(item: item) timelineItemViewState.type = .init(item: item)
return timelineItemViewState return timelineItemViewState
@@ -19,7 +19,7 @@ struct SwipeToReplyView: View {
} }
struct SwipeToReplyView_Previews: PreviewProvider, TestablePreview { struct SwipeToReplyView_Previews: PreviewProvider, TestablePreview {
static let timelineItem = TextRoomTimelineItem(id: .init(timelineID: ""), static let timelineItem = TextRoomTimelineItem(id: .init(uniqueID: ""),
timestamp: "", timestamp: "",
isOutgoing: true, isOutgoing: true,
isEditable: true, isEditable: true,
@@ -388,7 +388,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
// These always include a reply // These always include a reply
static var threads: some View { static var threads: some View {
ScrollView { ScrollView {
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -401,7 +401,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
eventContent: .message(.text(.init(body: "Short"))))), eventContent: .message(.text(.init(body: "Short"))))),
groupStyle: .single)) groupStyle: .single))
AudioRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""), AudioRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -417,7 +417,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
eventID: "123", eventID: "123",
eventContent: .message(.text(.init(body: "Short")))))) eventContent: .message(.text(.init(body: "Short"))))))
FileRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""), FileRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -431,7 +431,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
eventID: "123", eventID: "123",
eventContent: .message(.text(.init(body: "Short")))))) eventContent: .message(.text(.init(body: "Short"))))))
ImageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""), ImageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: true, isEditable: true,
@@ -469,7 +469,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
eventID: "123", eventID: "123",
eventContent: .message(.text(.init(body: "Short")))))) eventContent: .message(.text(.init(body: "Short"))))))
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""), VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -505,7 +505,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
static var replies: some View { static var replies: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -518,7 +518,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
eventContent: .message(.text(.init(body: "Short"))))), eventContent: .message(.text(.init(body: "Short"))))),
groupStyle: .single)) groupStyle: .single))
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -536,7 +536,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
static var encryptionAuthenticity: some View { static var encryptionAuthenticity: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -547,7 +547,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
properties: RoomTimelineItemProperties(encryptionAuthenticity: .unsignedDevice(color: .red))), properties: RoomTimelineItemProperties(encryptionAuthenticity: .unsignedDevice(color: .red))),
groupStyle: .single)) groupStyle: .single))
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -559,7 +559,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
encryptionAuthenticity: .unsignedDevice(color: .red))), encryptionAuthenticity: .unsignedDevice(color: .red))),
groupStyle: .single)) groupStyle: .single))
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -570,7 +570,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
properties: RoomTimelineItemProperties(encryptionAuthenticity: .unknownDevice(color: .red))), properties: RoomTimelineItemProperties(encryptionAuthenticity: .unknownDevice(color: .red))),
groupStyle: .first)) groupStyle: .first))
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -592,7 +592,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray)))) properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray))))
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""), VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -615,7 +615,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
static var pinned: some View { static var pinned: some View {
ScrollView { ScrollView {
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: "", eventID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -626,7 +626,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
replyDetails: nil), replyDetails: nil),
groupStyle: .single)) groupStyle: .single))
AudioRoomTimelineView(timelineItem: .init(id: .init(timelineID: "", eventID: ""), AudioRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -640,7 +640,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
contentType: nil), contentType: nil),
replyDetails: nil)) replyDetails: nil))
FileRoomTimelineView(timelineItem: .init(id: .init(timelineID: "", eventID: ""), FileRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -652,7 +652,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
thumbnailSource: nil, thumbnailSource: nil,
contentType: nil), contentType: nil),
replyDetails: nil)) replyDetails: nil))
ImageRoomTimelineView(timelineItem: .init(id: .init(timelineID: "", eventID: ""), ImageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: true, isEditable: true,
@@ -661,7 +661,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
sender: .init(id: ""), sender: .init(id: ""),
content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil), content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil),
replyDetails: nil)) replyDetails: nil))
LocationRoomTimelineView(timelineItem: .init(id: .init(timelineID: "", eventID: ""), LocationRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")),
timestamp: "Now", timestamp: "Now",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -673,7 +673,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
longitude: 12.496366), longitude: 12.496366),
description: "Location description description description description description description description description"), description: "Location description description description description description description description description"),
replyDetails: nil)) replyDetails: nil))
LocationRoomTimelineView(timelineItem: .init(id: .init(timelineID: "", eventID: ""), LocationRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")),
timestamp: "Now", timestamp: "Now",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -684,7 +684,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil), geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil),
replyDetails: nil)) replyDetails: nil))
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: "", eventID: ""), VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -79,8 +79,8 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
}() }()
static let sendingLast: TextRoomTimelineItem = { static let sendingLast: TextRoomTimelineItem = {
let id = viewModel.state.timelineViewState.timelineIDs.last ?? UUID().uuidString let id = viewModel.state.timelineViewState.uniqueIDs.last ?? UUID().uuidString
var result = TextRoomTimelineItem(id: .init(timelineID: id), var result = TextRoomTimelineItem(id: .init(uniqueID: id),
timestamp: "Now", timestamp: "Now",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -99,8 +99,8 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
}() }()
static let sentLast: TextRoomTimelineItem = { static let sentLast: TextRoomTimelineItem = {
let id = viewModel.state.timelineViewState.timelineIDs.last ?? UUID().uuidString let id = viewModel.state.timelineViewState.uniqueIDs.last ?? UUID().uuidString
let result = TextRoomTimelineItem(id: .init(timelineID: id), let result = TextRoomTimelineItem(id: .init(uniqueID: id),
timestamp: "Now", timestamp: "Now",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -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.timelineIDs.last == timelineItem.id.timelineID timelineItem.isOutgoing && context.viewState.timelineViewState.uniqueIDs.last == timelineItem.id.uniqueID
} }
var body: some View { var body: some View {
@@ -196,20 +196,20 @@ struct TimelineReactionViewPreviewsContainer: View {
var body: some View { var body: some View {
VStack(spacing: 8) { VStack(spacing: 8) {
TimelineReactionsView(context: TimelineViewModel.mock.context, TimelineReactionsView(context: TimelineViewModel.mock.context,
itemID: .init(timelineID: "1"), itemID: .init(uniqueID: "1"),
reactions: [AggregatedReaction.mockReactionWithLongText, reactions: [AggregatedReaction.mockReactionWithLongText,
AggregatedReaction.mockReactionWithLongTextRTL]) AggregatedReaction.mockReactionWithLongTextRTL])
Divider() Divider()
TimelineReactionsView(context: TimelineViewModel.mock.context, TimelineReactionsView(context: TimelineViewModel.mock.context,
itemID: .init(timelineID: "2"), itemID: .init(uniqueID: "2"),
reactions: Array(AggregatedReaction.mockReactions.prefix(3))) reactions: Array(AggregatedReaction.mockReactions.prefix(3)))
Divider() Divider()
TimelineReactionsView(context: TimelineViewModel.mock.context, TimelineReactionsView(context: TimelineViewModel.mock.context,
itemID: .init(timelineID: "3"), itemID: .init(uniqueID: "3"),
reactions: AggregatedReaction.mockReactions) reactions: AggregatedReaction.mockReactions)
Divider() Divider()
TimelineReactionsView(context: TimelineViewModel.mock.context, TimelineReactionsView(context: TimelineViewModel.mock.context,
itemID: .init(timelineID: "4"), itemID: .init(uniqueID: "4"),
reactions: AggregatedReaction.mockReactions, reactions: AggregatedReaction.mockReactions,
isLayoutRTL: true) isLayoutRTL: true)
} }
@@ -52,8 +52,8 @@ struct CollapsibleRoomTimelineView: View {
struct CollapsibleRoomTimelineView_Previews: PreviewProvider, TestablePreview { struct CollapsibleRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static let item = CollapsibleTimelineItem(items: [ static let item = CollapsibleTimelineItem(items: [
SeparatorRoomTimelineItem(id: .init(timelineID: "First separator"), text: "This is a separator"), SeparatorRoomTimelineItem(id: .init(uniqueID: "First separator"), text: "This is a separator"),
SeparatorRoomTimelineItem(id: .init(timelineID: "Second separator"), text: "This is another separator") SeparatorRoomTimelineItem(id: .init(uniqueID: "Second separator"), text: "This is another separator")
]) ])
static var previews: some View { static var previews: some View {
@@ -29,12 +29,12 @@ struct ReadMarkerRoomTimelineView: View {
struct ReadMarkerRoomTimelineView_Previews: PreviewProvider, TestablePreview { struct ReadMarkerRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static let viewModel = TimelineViewModel.mock static let viewModel = TimelineViewModel.mock
static let item = ReadMarkerRoomTimelineItem(id: .init(timelineID: .init(UUID().uuidString))) static let item = ReadMarkerRoomTimelineItem(id: .init(uniqueID: .init(UUID().uuidString)))
static var previews: some View { static var previews: some View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
RoomTimelineItemView(viewState: .init(type: .separator(.init(id: .init(timelineID: "Separator"), text: "Today")), groupStyle: .single)) RoomTimelineItemView(viewState: .init(type: .separator(.init(id: .init(uniqueID: "Separator"), text: "Today")), groupStyle: .single))
RoomTimelineItemView(viewState: .init(type: .text(.init(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(type: .text(.init(id: .random,
timestamp: "", timestamp: "",
isOutgoing: true, isOutgoing: true,
isEditable: false, isEditable: false,
@@ -45,8 +45,8 @@ struct ReadMarkerRoomTimelineView_Previews: PreviewProvider, TestablePreview {
ReadMarkerRoomTimelineView(timelineItem: item) ReadMarkerRoomTimelineView(timelineItem: item)
RoomTimelineItemView(viewState: .init(type: .separator(.init(id: .init(timelineID: "Separator"), text: "Today")), groupStyle: .single)) RoomTimelineItemView(viewState: .init(type: .separator(.init(id: .init(uniqueID: "Separator"), text: "Today")), groupStyle: .single))
RoomTimelineItemView(viewState: .init(type: .text(.init(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(type: .text(.init(id: .random,
timestamp: "", timestamp: "",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -23,7 +23,7 @@ struct SeparatorRoomTimelineView: View {
struct SeparatorRoomTimelineView_Previews: PreviewProvider, TestablePreview { struct SeparatorRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
let item = SeparatorRoomTimelineItem(id: .init(timelineID: "Separator"), text: "This is a separator") let item = SeparatorRoomTimelineItem(id: .init(uniqueID: "Separator"), text: "This is a separator")
SeparatorRoomTimelineView(timelineItem: item) SeparatorRoomTimelineView(timelineItem: item)
} }
} }
@@ -79,7 +79,7 @@ struct OIDCAuthorizationDataProxy: Equatable {
} }
} }
extension OidcAuthorizationData: Equatable { extension OidcAuthorizationData: @retroactive Equatable {
public static func == (lhs: MatrixRustSDK.OidcAuthorizationData, rhs: MatrixRustSDK.OidcAuthorizationData) -> Bool { public static func == (lhs: MatrixRustSDK.OidcAuthorizationData, rhs: MatrixRustSDK.OidcAuthorizationData) -> Bool {
lhs.loginUrl() == rhs.loginUrl() lhs.loginUrl() == rhs.loginUrl()
} }
@@ -23,7 +23,7 @@ struct RoomEventStringBuilder {
sender.displayName ?? sender.id sender.displayName ?? sender.id
} }
switch eventItemProxy.content.kind() { switch eventItemProxy.content {
case .unableToDecrypt(let encryptedMessage): case .unableToDecrypt(let encryptedMessage):
let errorMessage = switch encryptedMessage { let errorMessage = switch encryptedMessage {
case .megolmV1AesSha2(_, .membership): L10n.commonUnableToDecryptNoAccess case .megolmV1AesSha2(_, .membership): L10n.commonUnableToDecryptNoAccess
@@ -41,13 +41,8 @@ struct RoomEventStringBuilder {
return prefix(L10n.commonSticker, with: displayName) return prefix(L10n.commonSticker, with: displayName)
case .failedToParseMessageLike, .failedToParseState: case .failedToParseMessageLike, .failedToParseState:
return prefix(L10n.commonUnsupportedEvent, with: displayName) return prefix(L10n.commonUnsupportedEvent, with: displayName)
case .message: case .message(let messageContent):
guard let messageContent = eventItemProxy.content.asMessage() else { return messageEventStringBuilder.buildAttributedString(for: messageContent.msgType, senderDisplayName: displayName)
fatalError("Invalid message timeline item: \(eventItemProxy)")
}
let messageType = messageContent.msgtype()
return messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName)
case .state(_, let state): case .state(_, let state):
return stateEventStringBuilder return stateEventStringBuilder
.buildString(for: state, sender: sender, isOutgoing: isOutgoing) .buildString(for: state, sender: sender, isOutgoing: isOutgoing)
@@ -246,7 +246,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
var lastMessageFormattedTimestamp: String? var lastMessageFormattedTimestamp: String?
if let latestRoomMessage = roomDetails.latestEvent { if let latestRoomMessage = roomDetails.latestEvent {
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage, id: "0") let lastMessage = EventTimelineItemProxy(item: latestRoomMessage, uniqueID: "0")
lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal() lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal()
attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage) attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage)
} }
@@ -121,7 +121,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
MXLog.info("Enabling recovery") MXLog.info("Enabling recovery")
var keyUploadErrored = false var keyUploadErrored = false
let recoveryKey = try await encryption.enableRecovery(waitForBackupsToUpload: false, progressListener: SecureBackupEnableRecoveryProgressListener { [weak self] state in let recoveryKey = try await encryption.enableRecovery(waitForBackupsToUpload: false, passphrase: nil, progressListener: SecureBackupEnableRecoveryProgressListener { [weak self] state in
guard let self else { return } guard let self else { return }
switch state { switch state {
@@ -10,9 +10,9 @@ import Foundation
enum RoomTimelineItemFixtures { enum RoomTimelineItemFixtures {
/// The default timeline items used in Xcode previews etc. /// The default timeline items used in Xcode previews etc.
static var `default`: [RoomTimelineItemProtocol] = [ static var `default`: [RoomTimelineItemProtocol] = [
SeparatorRoomTimelineItem(id: .init(timelineID: "Yesterday"), text: "Yesterday"), SeparatorRoomTimelineItem(id: .init(uniqueID: "Yesterday"), text: "Yesterday"),
TextRoomTimelineItem(id: .init(timelineID: ".RoomTimelineItemFixtures.default.0", TextRoomTimelineItem(id: .init(uniqueID: ".RoomTimelineItemFixtures.default.0",
eventID: "RoomTimelineItemFixtures.default.0"), eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.0")),
timestamp: "10:10 AM", timestamp: "10:10 AM",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -21,8 +21,8 @@ enum RoomTimelineItemFixtures {
sender: .init(id: "", displayName: "Jacob"), sender: .init(id: "", displayName: "Jacob"),
content: .init(body: "That looks so good!"), content: .init(body: "That looks so good!"),
properties: RoomTimelineItemProperties(isEdited: true)), properties: RoomTimelineItemProperties(isEdited: true)),
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.1", TextRoomTimelineItem(id: .init(uniqueID: "RoomTimelineItemFixtures.default.1",
eventID: "RoomTimelineItemFixtures.default.1"), eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.1")),
timestamp: "10:11 AM", timestamp: "10:11 AM",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -33,8 +33,8 @@ enum RoomTimelineItemFixtures {
properties: RoomTimelineItemProperties(reactions: [ properties: RoomTimelineItemProperties(reactions: [
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: [ReactionSender(id: "me", timestamp: Date())]) AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: [ReactionSender(id: "me", timestamp: Date())])
])), ])),
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.2", TextRoomTimelineItem(id: .init(uniqueID: "RoomTimelineItemFixtures.default.2",
eventID: "RoomTimelineItemFixtures.default.2"), eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.2")),
timestamp: "10:11 AM", timestamp: "10:11 AM",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -52,9 +52,9 @@ enum RoomTimelineItemFixtures {
ReactionSender(id: "jacob", timestamp: Date()) ReactionSender(id: "jacob", timestamp: Date())
]) ])
])), ])),
SeparatorRoomTimelineItem(id: .init(timelineID: "Today"), text: "Today"), SeparatorRoomTimelineItem(id: .init(uniqueID: "Today"), text: "Today"),
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.3", TextRoomTimelineItem(id: .init(uniqueID: "RoomTimelineItemFixtures.default.3",
eventID: "RoomTimelineItemFixtures.default.3"), eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.3")),
timestamp: "5 PM", timestamp: "5 PM",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -63,8 +63,8 @@ enum RoomTimelineItemFixtures {
sender: .init(id: "", displayName: "Helena"), sender: .init(id: "", displayName: "Helena"),
content: .init(body: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!"), content: .init(body: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!"),
properties: RoomTimelineItemProperties(orderedReadReceipts: [ReadReceipt(userID: "alice", formattedTimestamp: nil)])), properties: RoomTimelineItemProperties(orderedReadReceipts: [ReadReceipt(userID: "alice", formattedTimestamp: nil)])),
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.4", TextRoomTimelineItem(id: .init(uniqueID: "RoomTimelineItemFixtures.default.4",
eventID: "RoomTimelineItemFixtures.default.4"), eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.4")),
timestamp: "5 PM", timestamp: "5 PM",
isOutgoing: true, isOutgoing: true,
isEditable: true, isEditable: true,
@@ -72,8 +72,8 @@ enum RoomTimelineItemFixtures {
isThreaded: false, isThreaded: false,
sender: .init(id: "", displayName: "Bob"), sender: .init(id: "", displayName: "Bob"),
content: .init(body: "And John's speech was amazing!")), content: .init(body: "And John's speech was amazing!")),
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.5", TextRoomTimelineItem(id: .init(uniqueID: "RoomTimelineItemFixtures.default.5",
eventID: "RoomTimelineItemFixtures.default.5"), eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.5")),
timestamp: "5 PM", timestamp: "5 PM",
isOutgoing: true, isOutgoing: true,
isEditable: true, isEditable: true,
@@ -86,8 +86,8 @@ enum RoomTimelineItemFixtures {
ReadReceipt(userID: "bob", formattedTimestamp: nil), ReadReceipt(userID: "bob", formattedTimestamp: nil),
ReadReceipt(userID: "charlie", formattedTimestamp: nil), ReadReceipt(userID: "charlie", formattedTimestamp: nil),
ReadReceipt(userID: "dan", formattedTimestamp: nil)])), ReadReceipt(userID: "dan", formattedTimestamp: nil)])),
TextRoomTimelineItem(id: .init(timelineID: "RoomTimelineItemFixtures.default.6", TextRoomTimelineItem(id: .init(uniqueID: "RoomTimelineItemFixtures.default.6",
eventID: "RoomTimelineItemFixtures.default.6"), eventOrTransactionID: .eventId(eventId: "RoomTimelineItemFixtures.default.6")),
timestamp: "5 PM", timestamp: "5 PM",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -242,7 +242,7 @@ enum RoomTimelineItemFixtures {
static var permalinkChunk: [RoomTimelineItemProtocol] { static var permalinkChunk: [RoomTimelineItemProtocol] {
(1...20).map { index in (1...20).map { index in
TextRoomTimelineItem(id: .init(timelineID: "\(index)", eventID: "$\(index)"), TextRoomTimelineItem(id: .init(uniqueID: "\(index)", eventOrTransactionID: .eventId(eventId: "$\(index)")),
text: "Message ID \(index)", text: "Message ID \(index)",
senderDisplayName: index > 10 ? "Alice" : "Bob") senderDisplayName: index > 10 ? "Alice" : "Bob")
} }
@@ -184,38 +184,11 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
} }
} }
private extension TimelineItem {
var debugIdentifier: DebugIdentifier {
if let virtualTimelineItem = asVirtual() {
return .virtual(timelineID: String(uniqueId()), dscription: virtualTimelineItem.description)
} else if let eventTimelineItem = asEvent() {
return .event(timelineID: String(uniqueId()),
eventID: eventTimelineItem.eventId(),
transactionID: eventTimelineItem.transactionId())
}
return .unknown(timelineID: String(uniqueId()))
}
}
private extension TimelineItemProxy { private extension TimelineItemProxy {
var debugIdentifier: DebugIdentifier {
switch self {
case .event(let eventTimelineItem):
return .event(timelineID: eventTimelineItem.id.timelineID,
eventID: eventTimelineItem.id.eventID,
transactionID: eventTimelineItem.id.transactionID)
case .virtual(let virtualTimelineItem, let timelineID):
return .virtual(timelineID: timelineID, dscription: virtualTimelineItem.description)
case .unknown(let item):
return .unknown(timelineID: String(item.uniqueId()))
}
}
var isMembershipChange: Bool { var isMembershipChange: Bool {
switch self { switch self {
case .event(let eventTimelineItemProxy): case .event(let eventTimelineItemProxy):
switch eventTimelineItemProxy.content.kind() { switch eventTimelineItemProxy.content {
case .roomMembership: case .roomMembership:
true true
default: default:
@@ -238,12 +211,6 @@ private extension VirtualTimelineItem {
} }
} }
enum DebugIdentifier {
case event(timelineID: String, eventID: String?, transactionID: String?)
case virtual(timelineID: String, dscription: String)
case unknown(timelineID: String)
}
private final class RoomTimelineListener: TimelineListener { private final class RoomTimelineListener: TimelineListener {
private let onUpdateClosure: ([TimelineDiff]) -> Void private let onUpdateClosure: ([TimelineDiff]) -> Void
@@ -186,7 +186,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
MXLog.info("Editing timeline item: \(timelineItemID)") MXLog.info("Editing timeline item: \(timelineItemID)")
let editMode: EditMode let editMode: EditMode
if !timelineItemID.timelineID.isEmpty, if !timelineItemID.uniqueID.isEmpty,
let timelineItem = liveTimelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID) { let timelineItem = liveTimelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID) {
editMode = .byEvent(timelineItem) editMode = .byEvent(timelineItem)
} else if let eventID = timelineItemID.eventID { } else if let eventID = timelineItemID.eventID {
@@ -392,15 +392,15 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
} }
return timelineItem return timelineItem
case .virtual(let virtualItem, let timelineID): case .virtual(let virtualItem, let uniqueID):
switch virtualItem { switch virtualItem {
case .dayDivider(let timestamp): case .dayDivider(let timestamp):
let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000)) let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
let dateString = date.formatted(date: .complete, time: .omitted) let dateString = date.formatted(date: .complete, time: .omitted)
return SeparatorRoomTimelineItem(id: .init(timelineID: dateString), text: dateString) return SeparatorRoomTimelineItem(id: .init(uniqueID: dateString), text: dateString)
case .readMarker: case .readMarker:
return ReadMarkerRoomTimelineItem(id: .init(timelineID: timelineID)) return ReadMarkerRoomTimelineItem(id: .init(uniqueID: uniqueID))
} }
case .unknown: case .unknown:
return nil return nil
@@ -409,7 +409,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
private func isItemCollapsible(_ item: TimelineItemProxy) -> Bool { private func isItemCollapsible(_ item: TimelineItemProxy) -> Bool {
if case let .event(eventItem) = item { if case let .event(eventItem) = item {
switch eventItem.content.kind() { switch eventItem.content {
case .profileChange, .roomMembership, .state: case .profileChange, .roomMembership, .state:
return true return true
default: default:
@@ -9,43 +9,43 @@ import MatrixRustSDK
// MARK: Redact message content from logs // MARK: Redact message content from logs
extension EmoteMessageContent: CustomStringConvertible { extension EmoteMessageContent: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
String(describing: Self.self) String(describing: Self.self)
} }
} }
extension FileMessageContent: CustomStringConvertible { extension FileMessageContent: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
String(describing: Self.self) String(describing: Self.self)
} }
} }
extension ImageMessageContent: CustomStringConvertible { extension ImageMessageContent: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
String(describing: Self.self) String(describing: Self.self)
} }
} }
extension NoticeMessageContent: CustomStringConvertible { extension NoticeMessageContent: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
String(describing: Self.self) String(describing: Self.self)
} }
} }
extension TextMessageContent: CustomStringConvertible { extension TextMessageContent: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
String(describing: Self.self) String(describing: Self.self)
} }
} }
extension VideoMessageContent: CustomStringConvertible { extension VideoMessageContent: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
String(describing: Self.self) String(describing: Self.self)
} }
} }
extension AudioMessageContent: CustomStringConvertible { extension AudioMessageContent: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
String(describing: Self.self) String(describing: Self.self)
} }
@@ -11,35 +11,48 @@ import MatrixRustSDK
struct TimelineItemIdentifier: Hashable { struct TimelineItemIdentifier: Hashable {
/// Stable id across state changes of the timeline item, it uniquely identifies an item in a timeline. /// Stable id across state changes of the timeline item, it uniquely identifies an item in a timeline.
/// It's value is consistent only per timeline instance, it should **not** be used to identify an item across timeline instances. /// It's value is consistent only per timeline instance, it should **not** be used to identify an item across timeline instances.
let timelineID: String let uniqueID: String
/// Uniquely identifies the timeline item from the server side. /// Contains the 2 possible identifiers of an event, either it has a remote event id or a local transaction id, never both or none.
/// Only available for EventTimelineItem and only when the item is returned by the server. var eventOrTransactionID: EventOrTransactionId?
var eventID: String?
var eventID: String? {
/// Uniquely identifies the local echo of the timeline item. switch eventOrTransactionID {
/// Only available for sent EventTimelineItem that have not been returned by the server yet. case .eventId(let eventId):
var transactionID: String? return eventId
default:
return nil
}
}
var transactionID: String? {
switch eventOrTransactionID {
case .transactionId(let transactionId):
return transactionId
default:
return nil
}
}
} }
extension TimelineItemIdentifier { extension TimelineItemIdentifier {
/// Use only for mocks/tests /// Use only for mocks/tests
static var random: Self { static var random: Self {
.init(timelineID: UUID().uuidString, eventID: UUID().uuidString) .init(uniqueID: UUID().uuidString, eventOrTransactionID: .eventId(eventId: UUID().uuidString))
} }
} }
/// A light wrapper around timeline items returned from Rust. /// A light wrapper around timeline items returned from Rust.
enum TimelineItemProxy { enum TimelineItemProxy {
case event(EventTimelineItemProxy) case event(EventTimelineItemProxy)
case virtual(MatrixRustSDK.VirtualTimelineItem, timelineID: String) case virtual(MatrixRustSDK.VirtualTimelineItem, uniqueID: String)
case unknown(MatrixRustSDK.TimelineItem) case unknown(MatrixRustSDK.TimelineItem)
init(item: MatrixRustSDK.TimelineItem) { init(item: MatrixRustSDK.TimelineItem) {
if let eventItem = item.asEvent() { if let eventItem = item.asEvent() {
self = .event(EventTimelineItemProxy(item: eventItem, id: String(item.uniqueId()))) self = .event(EventTimelineItemProxy(item: eventItem, uniqueID: String(item.uniqueId())))
} else if let virtualItem = item.asVirtual() { } else if let virtualItem = item.asVirtual() {
self = .virtual(virtualItem, timelineID: String(item.uniqueId())) self = .virtual(virtualItem, uniqueID: String(item.uniqueId()))
} else { } else {
self = .unknown(item) self = .unknown(item)
} }
@@ -92,13 +105,14 @@ class EventTimelineItemProxy {
let item: MatrixRustSDK.EventTimelineItem let item: MatrixRustSDK.EventTimelineItem
let id: TimelineItemIdentifier let id: TimelineItemIdentifier
init(item: MatrixRustSDK.EventTimelineItem, id: String) { init(item: MatrixRustSDK.EventTimelineItem, uniqueID: String) {
self.item = item self.item = item
self.id = TimelineItemIdentifier(timelineID: id, eventID: item.eventId(), transactionID: item.transactionId())
id = TimelineItemIdentifier(uniqueID: uniqueID, eventOrTransactionID: item.eventOrTransactionId)
} }
lazy var deliveryStatus: TimelineItemDeliveryStatus? = { lazy var deliveryStatus: TimelineItemDeliveryStatus? = {
guard let localSendState = item.localSendState() else { guard let localSendState = item.localSendState else {
return nil return nil
} }
@@ -118,43 +132,43 @@ class EventTimelineItemProxy {
} }
}() }()
lazy var canBeRepliedTo = item.canBeRepliedTo() lazy var canBeRepliedTo = item.canBeRepliedTo
lazy var content = item.content() lazy var content = item.content
lazy var isOwn = item.isOwn() lazy var isOwn = item.isOwn
lazy var isEditable = item.isEditable() lazy var isEditable = item.isEditable
lazy var sender: TimelineItemSender = { lazy var sender: TimelineItemSender = {
let profile = item.senderProfile() let profile = item.senderProfile
switch profile { switch profile {
case let .ready(displayName, isDisplayNameAmbiguous, avatarUrl): case let .ready(displayName, isDisplayNameAmbiguous, avatarUrl):
return .init(id: item.sender(), return .init(id: item.sender,
displayName: displayName, displayName: displayName,
isDisplayNameAmbiguous: isDisplayNameAmbiguous, isDisplayNameAmbiguous: isDisplayNameAmbiguous,
avatarURL: avatarUrl.flatMap(URL.init(string:))) avatarURL: avatarUrl.flatMap(URL.init(string:)))
default: default:
return .init(id: item.sender(), return .init(id: item.sender,
displayName: nil, displayName: nil,
isDisplayNameAmbiguous: false, isDisplayNameAmbiguous: false,
avatarURL: nil) avatarURL: nil)
} }
}() }()
lazy var reactions = item.reactions() lazy var reactions = item.reactions
lazy var timestamp = Date(timeIntervalSince1970: TimeInterval(item.timestamp() / 1000)) lazy var timestamp = Date(timeIntervalSince1970: TimeInterval(item.timestamp / 1000))
lazy var debugInfo: TimelineItemDebugInfo = { lazy var debugInfo: TimelineItemDebugInfo = {
let debugInfo = item.debugInfo() let debugInfo = item.debugInfoProvider.get()
return TimelineItemDebugInfo(model: debugInfo.model, originalJSON: debugInfo.originalJson, latestEditJSON: debugInfo.latestEditJson) return TimelineItemDebugInfo(model: debugInfo.model, originalJSON: debugInfo.originalJson, latestEditJSON: debugInfo.latestEditJson)
}() }()
lazy var shieldState = item.getShield(strict: false) lazy var shieldState = item.shieldsProvider.getShields(strict: false)
lazy var readReceipts = item.readReceipts() lazy var readReceipts = item.readReceipts
} }
struct TimelineItemDebugInfo: Identifiable, CustomStringConvertible { struct TimelineItemDebugInfo: Identifiable, CustomStringConvertible {
@@ -22,6 +22,6 @@ struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Equa
} }
init(position: Position) { init(position: Position) {
id = TimelineItemIdentifier(timelineID: position.id) id = TimelineItemIdentifier(uniqueID: position.id)
} }
} }
@@ -8,6 +8,6 @@
import Foundation import Foundation
struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable { struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
let id = TimelineItemIdentifier(timelineID: UUID().uuidString) let id = TimelineItemIdentifier(uniqueID: UUID().uuidString)
let name: String? let name: String?
} }
@@ -27,7 +27,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
func buildTimelineItem(for eventItemProxy: EventTimelineItemProxy, isDM: Bool) -> RoomTimelineItemProtocol? { func buildTimelineItem(for eventItemProxy: EventTimelineItemProxy, isDM: Bool) -> RoomTimelineItemProtocol? {
let isOutgoing = eventItemProxy.isOwn let isOutgoing = eventItemProxy.isOwn
switch eventItemProxy.content.kind() { switch eventItemProxy.content {
case .unableToDecrypt(let encryptedMessage): case .unableToDecrypt(let encryptedMessage):
return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, isOutgoing) return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, isOutgoing)
case .redactedMessage: case .redactedMessage:
@@ -43,8 +43,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing) return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
case .failedToParseState(let eventType, _, let error): case .failedToParseState(let eventType, _, let error):
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing) return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
case .message: case .message(let messageContent):
return buildMessageTimelineItem(eventItemProxy, isOutgoing) return buildMessageTimelineItem(eventItemProxy, messageContent, isOutgoing)
case .state(_, let content): case .state(_, let content):
if isDM, content == .roomCreate { if isDM, content == .roomCreate {
return nil return nil
@@ -72,34 +72,29 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
// MARK: - Message Events // MARK: - Message Events
private func buildMessageTimelineItem(_ eventItemProxy: EventTimelineItemProxy, _ isOutgoing: Bool) -> RoomTimelineItemProtocol? { private func buildMessageTimelineItem(_ eventItemProxy: EventTimelineItemProxy, _ messageContent: MessageContent, _ isOutgoing: Bool) -> RoomTimelineItemProtocol? {
guard let messageTimelineItem = eventItemProxy.content.asMessage() else { switch messageContent.msgType {
fatalError("Invalid message timeline item: \(eventItemProxy)") case .text(content: let textMessageContent):
} return buildTextTimelineItem(for: eventItemProxy, messageContent, textMessageContent, isOutgoing)
case .image(content: let imageMessageContent):
let isThreaded = messageTimelineItem.isThreaded() return buildImageTimelineItem(for: eventItemProxy, messageContent, imageMessageContent, isOutgoing)
switch messageTimelineItem.msgtype() { case .video(let videoMessageContent):
case .text(content: let content): return buildVideoTimelineItem(for: eventItemProxy, messageContent, videoMessageContent, isOutgoing)
return buildTextTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded) case .file(let fileMessageContent):
case .image(content: let content): return buildFileTimelineItem(for: eventItemProxy, messageContent, fileMessageContent, isOutgoing)
return buildImageTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded) case .notice(content: let noticeMessageContent):
case .video(let content): return buildNoticeTimelineItem(for: eventItemProxy, messageContent, noticeMessageContent, isOutgoing)
return buildVideoTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded) case .emote(content: let emoteMessageContent):
case .file(let content): return buildEmoteTimelineItem(for: eventItemProxy, messageContent, emoteMessageContent, isOutgoing)
return buildFileTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded) case .audio(let audioMessageContent):
case .notice(content: let content): if audioMessageContent.voice != nil {
return buildNoticeTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded) return buildVoiceTimelineItem(for: eventItemProxy, messageContent, audioMessageContent, isOutgoing)
case .emote(content: let content):
return buildEmoteTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded)
case .audio(let content):
if content.voice != nil {
return buildVoiceTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded)
} else { } else {
return buildAudioTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded) return buildAudioTimelineItem(for: eventItemProxy, messageContent, audioMessageContent, isOutgoing)
} }
case .location(let content): case .location(let locationMessageContent):
return buildLocationTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing, isThreaded) return buildLocationTimelineItem(for: eventItemProxy, messageContent, locationMessageContent, isOutgoing)
case .other: case .other:
return nil return nil
} }
@@ -196,20 +191,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildTextTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildTextTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: TextMessageContent, _ textMessageContent: TextMessageContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
TextRoomTimelineItem(id: eventItemProxy.id, TextRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildTextTimelineItemContent(messageContent), content: buildTextTimelineItemContent(textMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -217,20 +211,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: ImageMessageContent, _ imageMessageContent: ImageMessageContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
ImageRoomTimelineItem(id: eventItemProxy.id, ImageRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildImageTimelineItemContent(messageContent), content: buildImageTimelineItemContent(imageMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -238,20 +231,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: VideoMessageContent, _ videoMessageContent: VideoMessageContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
VideoRoomTimelineItem(id: eventItemProxy.id, VideoRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildVideoTimelineItemContent(messageContent), content: buildVideoTimelineItemContent(videoMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -259,20 +251,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: AudioMessageContent, _ audioMessageContent: AudioMessageContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
AudioRoomTimelineItem(id: eventItemProxy.id, AudioRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildAudioTimelineItemContent(messageContent), content: buildAudioTimelineItemContent(audioMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -280,20 +271,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: AudioMessageContent, _ audioMessageContent: AudioMessageContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
VoiceMessageRoomTimelineItem(id: eventItemProxy.id, VoiceMessageRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildAudioTimelineItemContent(messageContent), content: buildAudioTimelineItemContent(audioMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -301,20 +291,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: FileMessageContent, _ fileMessageContent: FileMessageContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
FileRoomTimelineItem(id: eventItemProxy.id, FileRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildFileTimelineItemContent(messageContent), content: buildFileTimelineItemContent(fileMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -322,20 +311,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: NoticeMessageContent, _ noticeMessageContent: NoticeMessageContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
NoticeRoomTimelineItem(id: eventItemProxy.id, NoticeRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildNoticeTimelineItemContent(messageContent), content: buildNoticeTimelineItemContent(noticeMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -343,20 +331,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: EmoteMessageContent, _ emoteMessageContent: EmoteMessageContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
EmoteRoomTimelineItem(id: eventItemProxy.id, EmoteRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildEmoteTimelineItemContent(senderDisplayName: eventItemProxy.sender.displayName, senderID: eventItemProxy.sender.id, messageContent: messageContent), content: buildEmoteTimelineItemContent(senderDisplayName: eventItemProxy.sender.displayName, senderID: eventItemProxy.sender.id, messageContent: emoteMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -364,20 +351,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
} }
private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy, private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy,
_ messageTimelineItem: Message, _ messageContent: MessageContent,
_ messageContent: LocationContent, _ locationMessageContent: LocationContent,
_ isOutgoing: Bool, _ isOutgoing: Bool) -> RoomTimelineItemProtocol {
_ isThreaded: Bool) -> RoomTimelineItemProtocol {
LocationRoomTimelineItem(id: eventItemProxy.id, LocationRoomTimelineItem(id: eventItemProxy.id,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing, isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable, isEditable: eventItemProxy.isEditable,
canBeRepliedTo: eventItemProxy.canBeRepliedTo, canBeRepliedTo: eventItemProxy.canBeRepliedTo,
isThreaded: isThreaded, isThreaded: messageContent.threadRoot != nil,
sender: eventItemProxy.sender, sender: eventItemProxy.sender,
content: buildLocationTimelineItemContent(messageContent), content: buildLocationTimelineItemContent(locationMessageContent),
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageTimelineItem.inReplyTo()), replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(), properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
reactions: aggregateReactions(eventItemProxy.reactions), reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus, deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
@@ -652,12 +638,12 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
// MARK: - Reply details // MARK: - Reply details
func buildReply(details: InReplyToDetails) -> TimelineItemReply { func buildReply(details: InReplyToDetails) -> TimelineItemReply {
let isThreaded = details.event.isThreaded let isThreaded = details.event().isThreaded
switch details.event { switch details.event() {
case .unavailable: case .unavailable:
return .init(details: .notLoaded(eventID: details.eventId), isThreaded: isThreaded) return .init(details: .notLoaded(eventID: details.eventId()), isThreaded: isThreaded)
case .pending: case .pending:
return .init(details: .loading(eventID: details.eventId), isThreaded: isThreaded) return .init(details: .loading(eventID: details.eventId()), isThreaded: isThreaded)
case let .ready(timelineItem, senderID, senderProfile): case let .ready(timelineItem, senderID, senderProfile):
let sender: TimelineItemSender let sender: TimelineItemSender
switch senderProfile { switch senderProfile {
@@ -675,9 +661,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
let replyContent: TimelineEventContent let replyContent: TimelineEventContent
switch timelineItem.kind() { switch timelineItem {
case .message: case .message(let messageContent):
return .init(details: timelineItemReplyDetails(sender: sender, eventID: details.eventId, messageType: timelineItem.asMessage()?.msgtype()), isThreaded: isThreaded) return .init(details: timelineItemReplyDetails(sender: sender, eventID: details.eventId(), messageType: messageContent.msgType), isThreaded: isThreaded)
case .poll(let question, _, _, _, _, _, _): case .poll(let question, _, _, _, _, _, _):
replyContent = .poll(question: question) replyContent = .poll(question: question)
case .sticker(let body, _, _): case .sticker(let body, _, _):
@@ -688,9 +674,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyContent = .message(.text(.init(body: L10n.commonUnsupportedEvent))) replyContent = .message(.text(.init(body: L10n.commonUnsupportedEvent)))
} }
return .init(details: .loaded(sender: sender, eventID: details.eventId, eventContent: replyContent), isThreaded: isThreaded) return .init(details: .loaded(sender: sender, eventID: details.eventId(), eventContent: replyContent), isThreaded: isThreaded)
case let .error(message): case let .error(message):
return .init(details: .error(eventID: details.eventId, message: message), isThreaded: isThreaded) return .init(details: .error(eventID: details.eventId(), message: message), isThreaded: isThreaded)
} }
} }
@@ -751,7 +737,11 @@ private extension RepliedToEventDetails {
var isThreaded: Bool { var isThreaded: Bool {
switch self { switch self {
case .ready(let content, _, _): case .ready(let content, _, _):
return content.asMessage()?.isThreaded() ?? false guard case let .message(content) = content else {
return false
}
return content.threadRoot != nil
default: default:
return false return false
} }
@@ -8,23 +8,14 @@
import Foundation import Foundation
final class RoomTimelineItemViewState: Identifiable, Equatable, ObservableObject { final class RoomTimelineItemViewState: Identifiable, Equatable, ObservableObject {
static func == (lhs: RoomTimelineItemViewState, rhs: RoomTimelineItemViewState) -> Bool {
lhs.type == rhs.type && lhs.groupStyle == rhs.groupStyle
}
@Published var type: RoomTimelineItemType @Published var type: RoomTimelineItemType
@Published var groupStyle: TimelineGroupStyle @Published var groupStyle: TimelineGroupStyle
/// Contains all the identification info of the item, `timelineID`, `eventID` and `transactionID` /// Contains all the identification info of the item, `uniqueID`, `eventID` and `transactionID`
var identifier: TimelineItemIdentifier { var identifier: TimelineItemIdentifier {
type.id type.id
} }
/// The `timelineID` of the item, used for the timeline view level identification, do not use for any business logic use `identifier` instead
var id: String {
identifier.timelineID
}
init(type: RoomTimelineItemType, groupStyle: TimelineGroupStyle) { init(type: RoomTimelineItemType, groupStyle: TimelineGroupStyle) {
self.type = type self.type = type
self.groupStyle = groupStyle self.groupStyle = groupStyle
@@ -33,6 +24,19 @@ final class RoomTimelineItemViewState: Identifiable, Equatable, ObservableObject
convenience init(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) { convenience init(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) {
self.init(type: .init(item: item), groupStyle: groupStyle) self.init(type: .init(item: item), groupStyle: groupStyle)
} }
// MARK: - Equatable
static func == (lhs: RoomTimelineItemViewState, rhs: RoomTimelineItemViewState) -> Bool {
lhs.type == rhs.type && lhs.groupStyle == rhs.groupStyle
}
// MARK: Identifiable
/// The `timelineID` of the item, used for the timeline view level identification, do not use for any business logic use `identifier` instead
var id: String {
identifier.uniqueID
}
} }
enum RoomTimelineItemType: Equatable { enum RoomTimelineItemType: Equatable {
@@ -72,7 +72,17 @@ final class TimelineProxy: TimelineProxyProtocol {
} }
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? { func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content().asMessage()?.content() guard let content = await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content,
case let .message(messageContent) = content else {
return nil
}
do {
return try contentWithoutRelationFromMessage(message: messageContent)
} catch {
MXLog.error("Failed retrieving message event content for timelineItemID=\(timelineItemID)")
return nil
}
} }
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> { func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
@@ -156,15 +166,15 @@ final class TimelineProxy: TimelineProxyProtocol {
func edit(_ timelineItem: EventTimelineItem, newContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError> { func edit(_ timelineItem: EventTimelineItem, newContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError> {
do { do {
guard try await timeline.edit(item: timelineItem, newContent: .roomMessage(content: newContent)) == true else { guard try await timeline.edit(eventOrTransactionId: timelineItem.eventOrTransactionId, newContent: .roomMessage(content: newContent)) == true else {
return .failure(.failedEditing) return .failure(.failedEditing)
} }
MXLog.info("Finished editing timeline item: \(timelineItem.eventId() ?? timelineItem.transactionId() ?? "unknown")") MXLog.info("Finished editing timeline item: \(timelineItem.eventOrTransactionId)")
return .success(()) return .success(())
} catch { } catch {
MXLog.error("Failed editing timeline item: \(timelineItem.eventId() ?? timelineItem.transactionId() ?? "unknown") with error: \(error)") MXLog.error("Failed editing timeline item: \(timelineItem.eventOrTransactionId) with error: \(error)")
return .failure(.sdkError(error)) return .failure(.sdkError(error))
} }
} }
@@ -172,18 +182,13 @@ final class TimelineProxy: TimelineProxyProtocol {
func redact(_ timelineItemID: TimelineItemIdentifier, reason: String?) async -> Result<Void, TimelineProxyError> { func redact(_ timelineItemID: TimelineItemIdentifier, reason: String?) async -> Result<Void, TimelineProxyError> {
MXLog.info("Redacting timeline item: \(timelineItemID)") MXLog.info("Redacting timeline item: \(timelineItemID)")
guard let eventTimelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID) else { guard let eventOrTransactionID = await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.eventOrTransactionId else {
MXLog.error("Unknown timeline item: \(timelineItemID)") MXLog.error("Unknown timeline item: \(timelineItemID)")
return .failure(.failedRedacting) return .failure(.failedRedacting)
} }
do { do {
let success = try await timeline.redactEvent(item: eventTimelineItem, reason: reason) try await timeline.redactEvent(eventOrTransactionId: eventOrTransactionID, reason: reason)
guard success else {
MXLog.error("Failed redacting timeline item: \(timelineItemID)")
return .failure(.failedRedacting)
}
MXLog.info("Redacted timeline item: \(timelineItemID)") MXLog.info("Redacted timeline item: \(timelineItemID)")
@@ -425,7 +430,7 @@ final class TimelineProxy: TimelineProxyProtocol {
MXLog.info("Toggling reaction for event: \(itemID)") MXLog.info("Toggling reaction for event: \(itemID)")
do { do {
try await timeline.toggleReaction(uniqueId: itemID.timelineID, key: reaction) try await timeline.toggleReaction(uniqueId: itemID.uniqueID, key: reaction)
MXLog.info("Finished toggling reaction for event: \(itemID)") MXLog.info("Finished toggling reaction for event: \(itemID)")
return .success(()) return .success(())
} catch { } catch {
@@ -460,7 +465,7 @@ final class TimelineProxy: TimelineProxyProtocol {
do { do {
let originalEvent = try await timeline.getEventTimelineItemByEventId(eventId: eventID) let originalEvent = try await timeline.getEventTimelineItemByEventId(eventId: eventID)
guard try await timeline.edit(item: originalEvent, guard try await timeline.edit(eventOrTransactionId: originalEvent.eventOrTransactionId,
newContent: .pollStart(pollData: .init(question: question, newContent: .pollStart(pollData: .init(question: question,
answers: answers, answers: answers,
maxSelections: 1, maxSelections: 1,
@@ -482,7 +487,7 @@ final class TimelineProxy: TimelineProxyProtocol {
return await Task.dispatch(on: .global()) { return await Task.dispatch(on: .global()) {
do { do {
try self.timeline.endPoll(pollStartId: pollStartID, text: text) try self.timeline.endPoll(pollStartEventId: pollStartID, text: text)
MXLog.info("Finished ending poll with eventID: \(pollStartID)") MXLog.info("Finished ending poll with eventID: \(pollStartID)")
@@ -498,7 +503,7 @@ final class TimelineProxy: TimelineProxyProtocol {
MXLog.info("Sending response for poll with eventID: \(pollStartID)") MXLog.info("Sending response for poll with eventID: \(pollStartID)")
do { do {
try await timeline.sendPollResponse(pollStartId: pollStartID, answers: answers) try await timeline.sendPollResponse(pollStartEventId: pollStartID, answers: answers)
MXLog.info("Finished sending response for poll with eventID: \(pollStartID)") MXLog.info("Finished sending response for poll with eventID: \(pollStartID)")
@@ -618,7 +623,7 @@ extension Array where Element == TimelineItemProxy {
func firstEventTimelineItemUsingStableID(_ id: TimelineItemIdentifier) -> EventTimelineItem? { func firstEventTimelineItemUsingStableID(_ id: TimelineItemIdentifier) -> EventTimelineItem? {
for item in self { for item in self {
if case let .event(eventTimelineItem) = item { if case let .event(eventTimelineItem) = item {
if eventTimelineItem.id.timelineID == id.timelineID { if eventTimelineItem.id.uniqueID == id.uniqueID {
return eventTimelineItem.item return eventTimelineItem.item
} }
} }
+3 -3
View File
@@ -17,15 +17,15 @@ class PollFormScreenUITests: XCTestCase {
func testFilledPoll() async throws { func testFilledPoll() async throws {
let app = Application.launch(.createPoll) let app = Application.launch(.createPoll)
let questionTextField = app.textViews[A11yIdentifiers.pollFormScreen.question] let questionTextField = app.textViews[A11yIdentifiers.pollFormScreen.question]
questionTextField.forceTap() questionTextField.tapCenter()
questionTextField.typeText("Do you like polls?") questionTextField.typeText("Do you like polls?")
let option1TextField = app.textViews[A11yIdentifiers.pollFormScreen.optionID(0)] let option1TextField = app.textViews[A11yIdentifiers.pollFormScreen.optionID(0)]
option1TextField.forceTap() option1TextField.tapCenter()
option1TextField.typeText("Yes") option1TextField.typeText("Yes")
let option2TextField = app.textViews[A11yIdentifiers.pollFormScreen.optionID(1)] let option2TextField = app.textViews[A11yIdentifiers.pollFormScreen.optionID(1)]
option2TextField.forceTap() option2TextField.tapCenter()
option2TextField.typeText("No") option2TextField.typeText("No")
// Dismiss the keyboard // Dismiss the keyboard
+1 -1
View File
@@ -20,7 +20,7 @@ class UserSessionScreenTests: XCTestCase {
try await Task.sleep(for: .seconds(1)) try await Task.sleep(for: .seconds(1))
try await app.assertScreenshot(.userSessionScreen, step: 2) try await app.assertScreenshot(.userSessionScreen, step: 2)
app.buttons[A11yIdentifiers.roomScreen.composerToolbar.openComposeOptions].forceTap() app.buttons[A11yIdentifiers.roomScreen.composerToolbar.openComposeOptions].tapCenter()
try await app.assertScreenshot(.userSessionScreen, step: 3) try await app.assertScreenshot(.userSessionScreen, step: 3)
} }
@@ -41,14 +41,14 @@ class ComposerToolbarViewModelTests: XCTestCase {
} }
func testComposerFocus() { func testComposerFocus() {
viewModel.process(timelineAction: .setMode(mode: .edit(originalItemId: TimelineItemIdentifier(timelineID: "mock")))) viewModel.process(timelineAction: .setMode(mode: .edit(originalItemId: TimelineItemIdentifier(uniqueID: "mock"))))
XCTAssertTrue(viewModel.state.bindings.composerFocused) XCTAssertTrue(viewModel.state.bindings.composerFocused)
viewModel.process(timelineAction: .removeFocus) viewModel.process(timelineAction: .removeFocus)
XCTAssertFalse(viewModel.state.bindings.composerFocused) XCTAssertFalse(viewModel.state.bindings.composerFocused)
} }
func testComposerMode() { func testComposerMode() {
let mode: ComposerMode = .edit(originalItemId: TimelineItemIdentifier(timelineID: "mock")) let mode: ComposerMode = .edit(originalItemId: TimelineItemIdentifier(uniqueID: "mock"))
viewModel.process(timelineAction: .setMode(mode: mode)) viewModel.process(timelineAction: .setMode(mode: mode))
XCTAssertEqual(viewModel.state.composerMode, mode) XCTAssertEqual(viewModel.state.composerMode, mode)
viewModel.process(timelineAction: .clear) viewModel.process(timelineAction: .clear)
@@ -56,7 +56,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
} }
func testComposerModeIsPublished() { func testComposerModeIsPublished() {
let mode: ComposerMode = .edit(originalItemId: TimelineItemIdentifier(timelineID: "mock")) let mode: ComposerMode = .edit(originalItemId: TimelineItemIdentifier(uniqueID: "mock"))
let expectation = expectation(description: "Composer mode is published") let expectation = expectation(description: "Composer mode is published")
let cancellable = viewModel let cancellable = viewModel
.context .context
@@ -236,7 +236,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
} }
viewModel.context.composerFormattingEnabled = false viewModel.context.composerFormattingEnabled = false
viewModel.process(timelineAction: .setMode(mode: .edit(originalItemId: .init(timelineID: "", eventID: "testID")))) viewModel.process(timelineAction: .setMode(mode: .edit(originalItemId: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "testID")))))
viewModel.context.plainComposerText = .init(string: "Hello world!") viewModel.context.plainComposerText = .init(string: "Hello world!")
viewModel.saveDraft() viewModel.saveDraft()
@@ -257,12 +257,11 @@ class ComposerToolbarViewModelTests: XCTestCase {
} }
viewModel.context.composerFormattingEnabled = false viewModel.context.composerFormattingEnabled = false
viewModel.process(timelineAction: .setMode(mode: .reply(itemID: .init(timelineID: "", viewModel.process(timelineAction: .setMode(mode: .reply(itemID: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "testID")),
eventID: "testID"), replyDetails: .loaded(sender: .init(id: ""),
replyDetails: .loaded(sender: .init(id: ""), eventID: "testID",
eventID: "testID", eventContent: .message(.text(.init(body: "reply text")))),
eventContent: .message(.text(.init(body: "reply text")))), isThread: false)))
isThread: false)))
viewModel.context.plainComposerText = .init(string: "Hello world!") viewModel.context.plainComposerText = .init(string: "Hello world!")
viewModel.saveDraft() viewModel.saveDraft()
@@ -283,12 +282,11 @@ class ComposerToolbarViewModelTests: XCTestCase {
} }
viewModel.context.composerFormattingEnabled = false viewModel.context.composerFormattingEnabled = false
viewModel.process(timelineAction: .setMode(mode: .reply(itemID: .init(timelineID: "", viewModel.process(timelineAction: .setMode(mode: .reply(itemID: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "testID")),
eventID: "testID"), replyDetails: .loaded(sender: .init(id: ""),
replyDetails: .loaded(sender: .init(id: ""), eventID: "testID",
eventID: "testID", eventContent: .message(.text(.init(body: "reply text")))),
eventContent: .message(.text(.init(body: "reply text")))), isThread: false)))
isThread: false)))
viewModel.saveDraft() viewModel.saveDraft()
await fulfillment(of: [expectation], timeout: 10) await fulfillment(of: [expectation], timeout: 10)
@@ -397,7 +395,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
await fulfillment(of: [expectation], timeout: 10) await fulfillment(of: [expectation], timeout: 10)
XCTAssertFalse(viewModel.context.composerFormattingEnabled) XCTAssertFalse(viewModel.context.composerFormattingEnabled)
XCTAssertEqual(viewModel.state.composerMode, .edit(originalItemId: .init(timelineID: "", eventID: "testID"))) XCTAssertEqual(viewModel.state.composerMode, .edit(originalItemId: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "testID"))))
XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: "Hello world!")) XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: "Hello world!"))
} }
@@ -431,13 +429,13 @@ class ComposerToolbarViewModelTests: XCTestCase {
await fulfillment(of: [draftExpectation], timeout: 10) await fulfillment(of: [draftExpectation], timeout: 10)
XCTAssertFalse(viewModel.context.composerFormattingEnabled) XCTAssertFalse(viewModel.context.composerFormattingEnabled)
// Testing the loading state first // Testing the loading state first
XCTAssertEqual(viewModel.state.composerMode, .reply(itemID: .init(timelineID: "", eventID: testEventID), XCTAssertEqual(viewModel.state.composerMode, .reply(itemID: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: testEventID)),
replyDetails: .loading(eventID: testEventID), replyDetails: .loading(eventID: testEventID),
isThread: false)) isThread: false))
XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: text)) XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: text))
await fulfillment(of: [loadReplyExpectation], timeout: 10) await fulfillment(of: [loadReplyExpectation], timeout: 10)
XCTAssertEqual(viewModel.state.composerMode, .reply(itemID: .init(timelineID: "", eventID: testEventID), XCTAssertEqual(viewModel.state.composerMode, .reply(itemID: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: testEventID)),
replyDetails: loadedReply, replyDetails: loadedReply,
isThread: true)) isThread: true))
} }
@@ -445,8 +443,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
func testRestoreReplyAndCancelReplyMode() async { func testRestoreReplyAndCancelReplyMode() async {
let testEventID = "testID" let testEventID = "testID"
let text = "Hello world!" let text = "Hello world!"
let loadedReply = TimelineItemReplyDetails.loaded(sender: .init(id: "userID", let loadedReply = TimelineItemReplyDetails.loaded(sender: .init(id: "userID", displayName: "Username"),
displayName: "Username"),
eventID: testEventID, eventID: testEventID,
eventContent: .message(.text(.init(body: "Reply text")))) eventContent: .message(.text(.init(body: "Reply text"))))
@@ -472,7 +469,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
await fulfillment(of: [draftExpectation], timeout: 10) await fulfillment(of: [draftExpectation], timeout: 10)
XCTAssertFalse(viewModel.context.composerFormattingEnabled) XCTAssertFalse(viewModel.context.composerFormattingEnabled)
// Testing the loading state first // Testing the loading state first
XCTAssertEqual(viewModel.state.composerMode, .reply(itemID: .init(timelineID: "", eventID: testEventID), XCTAssertEqual(viewModel.state.composerMode, .reply(itemID: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: testEventID)),
replyDetails: .loading(eventID: testEventID), replyDetails: .loading(eventID: testEventID),
isThread: false)) isThread: false))
XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: text)) XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: text))
+7 -7
View File
@@ -142,7 +142,7 @@ class LoggingTests: XCTestCase {
isThreaded: false, isThreaded: false,
sender: .init(id: "sender"), sender: .init(id: "sender"),
content: .init(body: "EmoteString", formattedBody: AttributedString(emoteAttributedString))) content: .init(body: "EmoteString", formattedBody: AttributedString(emoteAttributedString)))
let imageMessage = ImageRoomTimelineItem(id: .init(timelineID: "myimagemessage"), let imageMessage = ImageRoomTimelineItem(id: .init(uniqueID: "myimagemessage"),
timestamp: "", timestamp: "",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
@@ -184,25 +184,25 @@ class LoggingTests: XCTestCase {
} }
let content = try String(contentsOf: logFile) let content = try String(contentsOf: logFile)
XCTAssertTrue(content.contains(textMessage.id.timelineID)) XCTAssertTrue(content.contains(textMessage.id.uniqueID))
XCTAssertFalse(content.contains(textMessage.body)) XCTAssertFalse(content.contains(textMessage.body))
XCTAssertFalse(content.contains(textAttributedString)) XCTAssertFalse(content.contains(textAttributedString))
XCTAssertTrue(content.contains(noticeMessage.id.timelineID)) XCTAssertTrue(content.contains(noticeMessage.id.uniqueID))
XCTAssertFalse(content.contains(noticeMessage.body)) XCTAssertFalse(content.contains(noticeMessage.body))
XCTAssertFalse(content.contains(noticeAttributedString)) XCTAssertFalse(content.contains(noticeAttributedString))
XCTAssertTrue(content.contains(emoteMessage.id.timelineID)) XCTAssertTrue(content.contains(emoteMessage.id.uniqueID))
XCTAssertFalse(content.contains(emoteMessage.body)) XCTAssertFalse(content.contains(emoteMessage.body))
XCTAssertFalse(content.contains(emoteAttributedString)) XCTAssertFalse(content.contains(emoteAttributedString))
XCTAssertTrue(content.contains(imageMessage.id.timelineID)) XCTAssertTrue(content.contains(imageMessage.id.uniqueID))
XCTAssertFalse(content.contains(imageMessage.body)) XCTAssertFalse(content.contains(imageMessage.body))
XCTAssertTrue(content.contains(videoMessage.id.timelineID)) XCTAssertTrue(content.contains(videoMessage.id.uniqueID))
XCTAssertFalse(content.contains(videoMessage.body)) XCTAssertFalse(content.contains(videoMessage.body))
XCTAssertTrue(content.contains(fileMessage.id.timelineID)) XCTAssertTrue(content.contains(fileMessage.id.uniqueID))
XCTAssertFalse(content.contains(fileMessage.body)) XCTAssertFalse(content.contains(fileMessage.body))
} }
@@ -12,7 +12,7 @@ import XCTest
@MainActor @MainActor
class MessageForwardingScreenViewModelTests: XCTestCase { class MessageForwardingScreenViewModelTests: XCTestCase {
let forwardingItem = MessageForwardingItem(id: .init(timelineID: "t1", eventID: "t1"), let forwardingItem = MessageForwardingItem(id: .init(uniqueID: "t1", eventOrTransactionID: .eventId(eventId: "t1")),
roomID: "1", roomID: "1",
content: .init(noPointer: .init())) content: .init(noPointer: .init()))
var viewModel: MessageForwardingScreenViewModelProtocol! var viewModel: MessageForwardingScreenViewModelProtocol!
@@ -6,6 +6,7 @@
// //
@testable import ElementX @testable import ElementX
import MatrixRustSDK
import Combine import Combine
import XCTest import XCTest
@@ -66,8 +67,8 @@ class RoomScreenViewModelTests: XCTestCase {
let providerUpdateSubject = PassthroughSubject<([TimelineItemProxy], PaginationState), Never>() let providerUpdateSubject = PassthroughSubject<([TimelineItemProxy], PaginationState), Never>()
pinnedTimelineProviderMock.underlyingUpdatePublisher = providerUpdateSubject.eraseToAnyPublisher() pinnedTimelineProviderMock.underlyingUpdatePublisher = providerUpdateSubject.eraseToAnyPublisher()
pinnedTimelineMock.timelineProvider = pinnedTimelineProviderMock pinnedTimelineMock.timelineProvider = pinnedTimelineProviderMock
pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test1")), id: "1")), pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: "1")),
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test2")), id: "2"))] .event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: "2"))]
// check if the banner is now in a loaded state and is showing the counter // check if the banner is now in a loaded state and is showing the counter
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
@@ -83,9 +84,9 @@ class RoomScreenViewModelTests: XCTestCase {
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
viewState.pinnedEventsBannerState.count == 3 viewState.pinnedEventsBannerState.count == 3
} }
providerUpdateSubject.send(([.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test1")), id: "1")), providerUpdateSubject.send(([.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: "1")),
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test2")), id: "2")), .event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: "2")),
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test3")), id: "3"))], .initial)) .event(.init(item: EventTimelineItem(configuration: .init(eventID: "test3")), uniqueID: "3"))], .initial))
try await deferred.fulfill() try await deferred.fulfill()
XCTAssertFalse(viewModel.context.viewState.pinnedEventsBannerState.isLoading) XCTAssertFalse(viewModel.context.viewState.pinnedEventsBannerState.isLoading)
XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner) XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner)
@@ -106,9 +107,9 @@ class RoomScreenViewModelTests: XCTestCase {
let pinnedTimelineProviderMock = RoomTimelineProviderMock() let pinnedTimelineProviderMock = RoomTimelineProviderMock()
pinnedTimelineMock.timelineProvider = pinnedTimelineProviderMock pinnedTimelineMock.timelineProvider = pinnedTimelineProviderMock
pinnedTimelineProviderMock.underlyingUpdatePublisher = Empty<([TimelineItemProxy], PaginationState), Never>().eraseToAnyPublisher() pinnedTimelineProviderMock.underlyingUpdatePublisher = Empty<([TimelineItemProxy], PaginationState), Never>().eraseToAnyPublisher()
pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test1")), id: "1")), pinnedTimelineProviderMock.itemProxies = [.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: "1")),
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test2")), id: "2")), .event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: "2")),
.event(.init(item: EventTimelineItemSDKMock(configuration: .init(eventID: "test3")), id: "3"))] .event(.init(item: EventTimelineItem(configuration: .init(eventID: "test3")), uniqueID: "3"))]
roomProxyMock.underlyingPinnedEventsTimeline = pinnedTimelineMock roomProxyMock.underlyingPinnedEventsTimeline = pinnedTimelineMock
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock, let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
initialSelectedPinnedEventID: "test1", initialSelectedPinnedEventID: "test1",
@@ -6,6 +6,8 @@
// //
@testable import ElementX @testable import ElementX
import MatrixRustSDK
import XCTest import XCTest
@MainActor @MainActor
@@ -18,19 +20,9 @@ class TimelineItemFactoryTests: XCTestCase {
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
stateEventStringBuilder: RoomStateEventStringBuilder(userID: ownUserID)) stateEventStringBuilder: RoomStateEventStringBuilder(userID: ownUserID))
let eventTimelineItem = EventTimelineItemSDKMock() let eventTimelineItem = EventTimelineItem.mockCallInvite(sender: senderUserID)
eventTimelineItem.isOwnReturnValue = true
eventTimelineItem.timestampReturnValue = 0
eventTimelineItem.isEditableReturnValue = false
eventTimelineItem.canBeRepliedToReturnValue = false
eventTimelineItem.senderReturnValue = senderUserID
eventTimelineItem.senderProfileReturnValue = .pending
let timelineItemContent = TimelineItemContentSDKMock() let eventTimelineItemProxy = EventTimelineItemProxy(item: eventTimelineItem, uniqueID: "0")
timelineItemContent.kindReturnValue = .callInvite
eventTimelineItem.contentReturnValue = timelineItemContent
let eventTimelineItemProxy = EventTimelineItemProxy(item: eventTimelineItem, id: "0")
let item = factory.buildTimelineItem(for: eventTimelineItemProxy, isDM: false) let item = factory.buildTimelineItem(for: eventTimelineItemProxy, isDM: false)
@@ -258,9 +258,9 @@ class TimelineViewModelTests: XCTestCase {
func testSendReadReceiptWithoutEvents() async throws { func testSendReadReceiptWithoutEvents() async throws {
// Given a room with only virtual items. // Given a room with only virtual items.
let items = [SeparatorRoomTimelineItem(timelineID: "v1"), let items = [SeparatorRoomTimelineItem(uniqueID: "v1"),
SeparatorRoomTimelineItem(timelineID: "v2"), SeparatorRoomTimelineItem(uniqueID: "v2"),
SeparatorRoomTimelineItem(timelineID: "v3")] SeparatorRoomTimelineItem(uniqueID: "v3")]
let (viewModel, _, timelineProxy, _) = readReceiptsConfiguration(with: items) let (viewModel, _, timelineProxy, _) = readReceiptsConfiguration(with: items)
// When sending a read receipt for the last item. // When sending a read receipt for the last item.
@@ -275,7 +275,7 @@ class TimelineViewModelTests: XCTestCase {
// Given a room where the last event is a virtual item. // Given a room where the last event is a virtual item.
let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"), let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
SeparatorRoomTimelineItem(timelineID: "v3")] SeparatorRoomTimelineItem(uniqueID: "v3")]
let (viewModel, _, _, _) = readReceiptsConfiguration(with: items) let (viewModel, _, _, _) = readReceiptsConfiguration(with: items)
// When sending a read receipt for the last item. // When sending a read receipt for the last item.
@@ -436,14 +436,14 @@ private extension TextRoomTimelineItem {
} }
private extension SeparatorRoomTimelineItem { private extension SeparatorRoomTimelineItem {
init(timelineID: String) { init(uniqueID: String) {
self.init(id: .init(timelineID: timelineID), text: "") self.init(id: .init(uniqueID: uniqueID), text: "")
} }
} }
private extension TextRoomTimelineItem { private extension TextRoomTimelineItem {
init(eventID: String) { init(eventID: String) {
self.init(id: .init(timelineID: UUID().uuidString, eventID: eventID), self.init(id: .init(uniqueID: UUID().uuidString, eventOrTransactionID: .eventId(eventId: eventID)),
timestamp: "", timestamp: "",
isOutgoing: false, isOutgoing: false,
isEditable: false, isEditable: false,
+1 -1
View File
@@ -60,7 +60,7 @@ packages:
# Element/Matrix dependencies # Element/Matrix dependencies
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/element-hq/matrix-rust-components-swift url: https://github.com/element-hq/matrix-rust-components-swift
exactVersion: 1.0.52 exactVersion: 1.0.53
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
Compound: Compound:
url: https://github.com/element-hq/compound-ios url: https://github.com/element-hq/compound-ios