mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Refactor the NSDiffableDataSourceLayer (#1235)
* first part, using references * better animations * delete view provider and replaced wth two files * ordered dictionary usage * bubbled styler view previews * plain style is back * fix * read marker previews restored * updated tests * code improvements * better naming
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 51;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -464,6 +464,7 @@
|
|||||||
A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4C9547BBFEEF30AA11329B /* TimelineItemStatusView.swift */; };
|
A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4C9547BBFEEF30AA11329B /* TimelineItemStatusView.swift */; };
|
||||||
A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; };
|
A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; };
|
||||||
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
||||||
|
A72F51AB2A54364E0038A02F /* RoomTimelineItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72F51AA2A54364E0038A02F /* RoomTimelineItemViewModel.swift */; };
|
||||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
||||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||||
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
||||||
@@ -587,7 +588,7 @@
|
|||||||
CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; };
|
CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; };
|
||||||
CF3827071B0BC9638BD44F5D /* WaitlistScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */; };
|
CF3827071B0BC9638BD44F5D /* WaitlistScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */; };
|
||||||
CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */; };
|
CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */; };
|
||||||
CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */; };
|
CF82143AA4A4F7BD11D22946 /* RoomTimelineItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6C5E4950B6C9842F35A38 /* RoomTimelineItemView.swift */; };
|
||||||
D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; };
|
D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; };
|
||||||
D181AC8FF236B7F91C0A8C28 /* MapTiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AA3F4B285570805CB0CCDD /* MapTiler.swift */; };
|
D181AC8FF236B7F91C0A8C28 /* MapTiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AA3F4B285570805CB0CCDD /* MapTiler.swift */; };
|
||||||
D2A15D03F81342A09340BD56 /* AnalyticsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */; };
|
D2A15D03F81342A09340BD56 /* AnalyticsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */; };
|
||||||
@@ -824,7 +825,7 @@
|
|||||||
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = "<group>"; };
|
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = "<group>"; };
|
||||||
12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = "<group>"; };
|
12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = "<group>"; };
|
12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
|
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
|
||||||
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
|
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
|
||||||
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
||||||
@@ -959,7 +960,7 @@
|
|||||||
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
|
47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = "<group>"; };
|
||||||
478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DesignKit; path = DesignKit; sourceTree = SOURCE_ROOT; };
|
478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DesignKit; sourceTree = SOURCE_ROOT; };
|
||||||
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
||||||
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||||
47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; };
|
47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
@@ -1126,7 +1127,7 @@
|
|||||||
8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = "<group>"; };
|
8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = "<group>"; };
|
||||||
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
||||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
|
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||||
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
|
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
|
||||||
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
|
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
|
8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1189,6 +1190,7 @@
|
|||||||
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>"; };
|
||||||
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
||||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
A72F51AA2A54364E0038A02F /* RoomTimelineItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemViewModel.swift; sourceTree = "<group>"; };
|
||||||
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
||||||
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -1205,7 +1207,7 @@
|
|||||||
ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockImageCache.swift; sourceTree = "<group>"; };
|
AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockImageCache.swift; sourceTree = "<group>"; };
|
||||||
AC3F82523D6F48B926D6AF68 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
AC3F82523D6F48B926D6AF68 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||||
ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = "<group>"; };
|
ACB6C5E4950B6C9842F35A38 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = "<group>"; };
|
||||||
ACCC1874C122E2BBE648B8F5 /* LegalInformationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenUITests.swift; sourceTree = "<group>"; };
|
ACCC1874C122E2BBE648B8F5 /* LegalInformationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
ADB35E2DB4EFE8E6F3959629 /* InviteUsersScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenUITests.swift; sourceTree = "<group>"; };
|
ADB35E2DB4EFE8E6F3959629 /* InviteUsersScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
@@ -1232,7 +1234,7 @@
|
|||||||
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; };
|
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; };
|
||||||
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
||||||
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
||||||
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
|
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||||
@@ -1309,7 +1311,7 @@
|
|||||||
CD6B0C4639E066915B5E6463 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
CD6B0C4639E066915B5E6463 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||||
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
||||||
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
||||||
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
||||||
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
||||||
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
|
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
|
||||||
D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = "<group>"; };
|
D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = "<group>"; };
|
||||||
@@ -1379,7 +1381,7 @@
|
|||||||
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
||||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||||
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
|
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = "<group>"; };
|
||||||
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
|
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||||
@@ -2778,8 +2780,9 @@
|
|||||||
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */,
|
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */,
|
||||||
105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */,
|
105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */,
|
||||||
7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */,
|
7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */,
|
||||||
|
A72F51AA2A54364E0038A02F /* RoomTimelineItemViewModel.swift */,
|
||||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */,
|
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */,
|
||||||
ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */,
|
ACB6C5E4950B6C9842F35A38 /* RoomTimelineItemView.swift */,
|
||||||
75D1D02F7F3AC1122FCFB4F3 /* Items */,
|
75D1D02F7F3AC1122FCFB4F3 /* Items */,
|
||||||
);
|
);
|
||||||
path = TimelineItems;
|
path = TimelineItems;
|
||||||
@@ -4261,6 +4264,7 @@
|
|||||||
0C58A846F61949B1D545D661 /* NoticeRoomTimelineItem.swift in Sources */,
|
0C58A846F61949B1D545D661 /* NoticeRoomTimelineItem.swift in Sources */,
|
||||||
9408CE8B8865C0C8DD4C9869 /* NoticeRoomTimelineItemContent.swift in Sources */,
|
9408CE8B8865C0C8DD4C9869 /* NoticeRoomTimelineItemContent.swift in Sources */,
|
||||||
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */,
|
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */,
|
||||||
|
A72F51AB2A54364E0038A02F /* RoomTimelineItemViewModel.swift in Sources */,
|
||||||
3F70E237CE4C3FAB02FC227F /* NotificationConstants.swift in Sources */,
|
3F70E237CE4C3FAB02FC227F /* NotificationConstants.swift in Sources */,
|
||||||
CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */,
|
CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */,
|
||||||
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */,
|
652ACCF104A8CEF30788963C /* NotificationManager.swift in Sources */,
|
||||||
@@ -4347,7 +4351,7 @@
|
|||||||
1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */,
|
1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */,
|
||||||
9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */,
|
9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */,
|
||||||
77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */,
|
77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */,
|
||||||
CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */,
|
CF82143AA4A4F7BD11D22946 /* RoomTimelineItemView.swift in Sources */,
|
||||||
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */,
|
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */,
|
||||||
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */,
|
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */,
|
||||||
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */,
|
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import Combine
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import OrderedCollections
|
||||||
|
|
||||||
enum RoomScreenViewModelAction {
|
enum RoomScreenViewModelAction {
|
||||||
case displayRoomDetails
|
case displayRoomDetails
|
||||||
case displayEmojiPicker(itemID: String)
|
case displayEmojiPicker(itemID: String)
|
||||||
@@ -84,7 +86,7 @@ struct RoomScreenViewState: BindableState {
|
|||||||
var roomId: String
|
var roomId: String
|
||||||
var roomTitle = ""
|
var roomTitle = ""
|
||||||
var roomAvatarURL: URL?
|
var roomAvatarURL: URL?
|
||||||
var items: [RoomTimelineViewProvider] = []
|
var itemsDictionary = OrderedDictionary<String, RoomTimelineItemViewModel>()
|
||||||
var members: [String: RoomMemberState] = [:]
|
var members: [String: RoomMemberState] = [:]
|
||||||
var canBackPaginate = true
|
var canBackPaginate = true
|
||||||
var isBackPaginating = false
|
var isBackPaginating = false
|
||||||
@@ -102,6 +104,14 @@ struct RoomScreenViewState: BindableState {
|
|||||||
var sendButtonDisabled: Bool {
|
var sendButtonDisabled: Bool {
|
||||||
bindings.composerText.count == 0
|
bindings.composerText.count == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var itemIDs: [String] {
|
||||||
|
itemsDictionary.keys.elements
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemViewModels: [RoomTimelineItemViewModel] {
|
||||||
|
itemsDictionary.values.elements
|
||||||
|
}
|
||||||
|
|
||||||
let scrollToBottomPublisher = PassthroughSubject<Void, Never>()
|
let scrollToBottomPublisher = PassthroughSubject<Void, Never>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import Algorithms
|
import Algorithms
|
||||||
import Combine
|
import Combine
|
||||||
|
import OrderedCollections
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias RoomScreenViewModelType = StateStoreViewModel<RoomScreenViewState, RoomScreenViewAction>
|
typealias RoomScreenViewModelType = StateStoreViewModel<RoomScreenViewState, RoomScreenViewAction>
|
||||||
@@ -116,7 +117,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
case .tappedOnUser(userID: let userID):
|
case .tappedOnUser(userID: let userID):
|
||||||
Task { await handleTappedUser(userID: userID) }
|
Task { await handleTappedUser(userID: userID) }
|
||||||
case .displayEmojiPicker(let itemID):
|
case .displayEmojiPicker(let itemID):
|
||||||
guard let item = state.items.first(where: { $0.id == itemID }), item.isReactable else { return }
|
guard let item = state.itemsDictionary[itemID], item.isReactable else { return }
|
||||||
callback?(.displayEmojiPicker(itemID: itemID))
|
callback?(.displayEmojiPicker(itemID: itemID))
|
||||||
case .reactionSummary(let itemId, let key):
|
case .reactionSummary(let itemId, let key):
|
||||||
showReactionSummary(for: itemId, selectedKey: key)
|
showReactionSummary(for: itemId, selectedKey: key)
|
||||||
@@ -232,8 +233,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func buildTimelineViews() {
|
private func buildTimelineViews() {
|
||||||
var timelineViews = [RoomTimelineViewProvider]()
|
var timelineItemsDictionary = OrderedDictionary<String, RoomTimelineItemViewModel>()
|
||||||
|
|
||||||
let itemsGroupedByTimelineDisplayStyle = timelineController.timelineItems.chunked { current, next in
|
let itemsGroupedByTimelineDisplayStyle = timelineController.timelineItems.chunked { current, next in
|
||||||
canGroupItem(timelineItem: current, with: next)
|
canGroupItem(timelineItem: current, with: next)
|
||||||
}
|
}
|
||||||
@@ -246,24 +247,38 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
|
|
||||||
if itemGroup.count == 1 {
|
if itemGroup.count == 1 {
|
||||||
if let firstItem = itemGroup.first {
|
if let firstItem = itemGroup.first {
|
||||||
timelineViews.append(RoomTimelineViewProvider(timelineItem: firstItem, groupStyle: .single))
|
timelineItemsDictionary.updateValue(updateViewModel(item: firstItem, groupStyle: .single),
|
||||||
|
forKey: firstItem.id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (index, item) in itemGroup.enumerated() {
|
for (index, item) in itemGroup.enumerated() {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
timelineViews.append(RoomTimelineViewProvider(timelineItem: item, groupStyle: .first))
|
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .first),
|
||||||
|
forKey: item.id)
|
||||||
} else if index == itemGroup.count - 1 {
|
} else if index == itemGroup.count - 1 {
|
||||||
timelineViews.append(RoomTimelineViewProvider(timelineItem: item, groupStyle: .last))
|
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .last),
|
||||||
|
forKey: item.id)
|
||||||
} else {
|
} else {
|
||||||
timelineViews.append(RoomTimelineViewProvider(timelineItem: item, groupStyle: .middle))
|
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .middle),
|
||||||
|
forKey: item.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.items = timelineViews
|
state.itemsDictionary = timelineItemsDictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateViewModel(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewModel {
|
||||||
|
if let timelineItemViewModel = state.itemsDictionary[item.id] {
|
||||||
|
timelineItemViewModel.groupStyle = groupStyle
|
||||||
|
timelineItemViewModel.type = .init(item: item)
|
||||||
|
return timelineItemViewModel
|
||||||
|
} else {
|
||||||
|
return RoomTimelineItemViewModel(item: item, groupStyle: groupStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func canGroupItem(timelineItem: RoomTimelineItemProtocol, with otherTimelineItem: RoomTimelineItemProtocol) -> Bool {
|
private func canGroupItem(timelineItem: RoomTimelineItemProtocol, with otherTimelineItem: RoomTimelineItemProtocol) -> Bool {
|
||||||
if timelineItem is CollapsibleTimelineItem || otherTimelineItem is CollapsibleTimelineItem {
|
if timelineItem is CollapsibleTimelineItem || otherTimelineItem is CollapsibleTimelineItem {
|
||||||
return false
|
return false
|
||||||
|
|||||||
+24
-24
@@ -307,7 +307,7 @@ private extension View {
|
|||||||
|
|
||||||
struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
|
struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
|
||||||
static let viewModel = RoomScreenViewModel.mock
|
static let viewModel = RoomScreenViewModel.mock
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
mockTimeline
|
mockTimeline
|
||||||
.previewDisplayName("Mock Timeline")
|
.previewDisplayName("Mock Timeline")
|
||||||
@@ -320,40 +320,40 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
|
|||||||
replies
|
replies
|
||||||
.previewDisplayName("Replies")
|
.previewDisplayName("Replies")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mockTimeline: some View {
|
static var mockTimeline: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(viewModel.state.items) { item in
|
ForEach(viewModel.state.itemViewModels) { itemViewModel in
|
||||||
item.padding(TimelineStyle.bubbles.rowInsets) // Insets added in the table view cells
|
RoomTimelineItemView(viewModel: itemViewModel)
|
||||||
|
.padding(TimelineStyle.bubbles.rowInsets)
|
||||||
|
// Insets added in the table view cells
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.timelineStyle, .bubbles)
|
.environment(\.timelineStyle, .bubbles)
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var replies: some View {
|
static var replies: some View {
|
||||||
VStack {
|
VStack {
|
||||||
RoomTimelineViewProvider.text(TextRoomTimelineItem(id: "",
|
RoomTimelineItemView(viewModel: .init(item: TextRoomTimelineItem(id: "",
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "whoever"),
|
sender: .init(id: "whoever"),
|
||||||
content: .init(body: "A long message that should be on multiple lines."),
|
content: .init(body: "A long message that should be on multiple lines."),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))),
|
contentType: .text(.init(body: "Short")))), groupStyle: .single))
|
||||||
.single)
|
|
||||||
|
RoomTimelineItemView(viewModel: .init(item: TextRoomTimelineItem(id: "",
|
||||||
RoomTimelineViewProvider.text(TextRoomTimelineItem(id: "",
|
timestamp: "10:42",
|
||||||
timestamp: "10:42",
|
isOutgoing: true,
|
||||||
isOutgoing: true,
|
isEditable: false,
|
||||||
isEditable: false,
|
sender: .init(id: "whoever"),
|
||||||
sender: .init(id: "whoever"),
|
content: .init(body: "Short message"),
|
||||||
content: .init(body: "Short message"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
contentType: .text(.init(body: "A long message that should be on more than 2 lines and so will be clipped by the layout.")))), groupStyle: .single))
|
||||||
contentType: .text(.init(body: "A long message that should be on more than 2 lines and so will be clipped by the layout.")))),
|
|
||||||
.single)
|
|
||||||
}
|
}
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,12 +135,12 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
|||||||
|
|
||||||
struct TimelineItemPlainStylerView_Previews: PreviewProvider {
|
struct TimelineItemPlainStylerView_Previews: PreviewProvider {
|
||||||
static let viewModel = RoomScreenViewModel.mock
|
static let viewModel = RoomScreenViewModel.mock
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(1..<MockRoomTimelineController().timelineItems.count, id: \.self) { index in
|
ForEach(1..<MockRoomTimelineController().timelineItems.count, id: \.self) { index in
|
||||||
let item = MockRoomTimelineController().timelineItems[index]
|
let item = MockRoomTimelineController().timelineItems[index]
|
||||||
RoomTimelineViewProvider(timelineItem: item, groupStyle: .single)
|
RoomTimelineItemView(viewModel: .init(item: item, groupStyle: .single))
|
||||||
.padding(TimelineStyle.plain.rowInsets) // Insets added in the table view cells
|
.padding(TimelineStyle.plain.rowInsets) // Insets added in the table view cells
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ struct TimelineItemStyler_Previews: PreviewProvider {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
static let sendingLast: TextRoomTimelineItem = {
|
static let sendingLast: TextRoomTimelineItem = {
|
||||||
let id = viewModel.state.items.last?.id ?? UUID().uuidString
|
let id = viewModel.state.itemIDs.last ?? UUID().uuidString
|
||||||
var result = TextRoomTimelineItem(id: id, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
var result = TextRoomTimelineItem(id: id, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
result.properties.deliveryStatus = .sending
|
result.properties.deliveryStatus = .sending
|
||||||
return result
|
return result
|
||||||
@@ -66,7 +66,7 @@ struct TimelineItemStyler_Previews: PreviewProvider {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
static let sentLast: TextRoomTimelineItem = {
|
static let sentLast: TextRoomTimelineItem = {
|
||||||
let id = viewModel.state.items.last?.id ?? UUID().uuidString
|
let id = viewModel.state.itemIDs.last ?? UUID().uuidString
|
||||||
let result = TextRoomTimelineItem(id: id, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
let result = TextRoomTimelineItem(id: id, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -23,14 +23,10 @@ struct TimelineItemStatusView: View {
|
|||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
|
|
||||||
private var isLastOutgoingMessage: Bool {
|
private var isLastOutgoingMessage: Bool {
|
||||||
context.viewState.items.last?.id == timelineItem.id &&
|
context.viewState.itemIDs.last == timelineItem.id &&
|
||||||
timelineItem.isOutgoing
|
timelineItem.isOutgoing
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isLast: Bool {
|
|
||||||
context.viewState.items.last?.id == timelineItem.id
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if !timelineItem.properties.orderedReadReceipts.isEmpty, readReceiptsEnabled {
|
if !timelineItem.properties.orderedReadReceipts.isEmpty, readReceiptsEnabled {
|
||||||
readReceipts
|
readReceipts
|
||||||
|
|||||||
@@ -18,20 +18,20 @@ import SwiftUI
|
|||||||
|
|
||||||
struct CollapsibleRoomTimelineView: View {
|
struct CollapsibleRoomTimelineView: View {
|
||||||
private let timelineItem: CollapsibleTimelineItem
|
private let timelineItem: CollapsibleTimelineItem
|
||||||
private let timelineViews: [RoomTimelineViewProvider]
|
private let timelineViewModels: [RoomTimelineItemViewModel]
|
||||||
|
|
||||||
@State private var isExpanded = false
|
@State private var isExpanded = false
|
||||||
|
|
||||||
init(timelineItem: CollapsibleTimelineItem) {
|
init(timelineItem: CollapsibleTimelineItem) {
|
||||||
self.timelineItem = timelineItem
|
self.timelineItem = timelineItem
|
||||||
timelineViews = timelineItem.items.map { RoomTimelineViewProvider(timelineItem: $0, groupStyle: .single) }
|
timelineViewModels = timelineItem.items.map { .init(item: $0, groupStyle: .single) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
DisclosureGroup(L10n.roomTimelineStateChanges(timelineItem.items.count), isExpanded: $isExpanded) {
|
DisclosureGroup(L10n.roomTimelineStateChanges(timelineItem.items.count), isExpanded: $isExpanded) {
|
||||||
Group {
|
Group {
|
||||||
ForEach(timelineViews) { timelineView in
|
ForEach(timelineViewModels) { viewModel in
|
||||||
timelineView.body
|
RoomTimelineItemView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}.transition(.opacity.animation(.elementDefault))
|
}.transition(.opacity.animation(.elementDefault))
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -37,27 +37,27 @@ struct ReadMarkerRoomTimelineView: View {
|
|||||||
|
|
||||||
struct ReadMarkerRoomTimelineView_Previews: PreviewProvider {
|
struct ReadMarkerRoomTimelineView_Previews: PreviewProvider {
|
||||||
static let viewModel = RoomScreenViewModel.mock
|
static let viewModel = RoomScreenViewModel.mock
|
||||||
|
|
||||||
static let item = ReadMarkerRoomTimelineItem()
|
static let item = ReadMarkerRoomTimelineItem()
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
RoomTimelineViewProvider.separator(.init(id: "Separator", text: "Today"), .single)
|
RoomTimelineItemView(viewModel: .init(type: .separator(.init(id: "Separator", text: "Today")), groupStyle: .single))
|
||||||
RoomTimelineViewProvider.text(.init(id: "",
|
RoomTimelineItemView(viewModel: .init(type: .text(.init(id: "",
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "1", displayName: "Bob"),
|
sender: .init(id: "1", displayName: "Bob"),
|
||||||
content: .init(body: "This is another message")), .single)
|
content: .init(body: "This is another message"))), groupStyle: .single))
|
||||||
|
|
||||||
ReadMarkerRoomTimelineView(timelineItem: item)
|
ReadMarkerRoomTimelineView(timelineItem: item)
|
||||||
|
|
||||||
RoomTimelineViewProvider.separator(.init(id: "Separator", text: "Today"), .single)
|
RoomTimelineItemView(viewModel: .init(type: .separator(.init(id: "Separator", text: "Today")), groupStyle: .single))
|
||||||
RoomTimelineViewProvider.text(.init(id: "",
|
RoomTimelineItemView(viewModel: .init(type: .text(.init(id: "",
|
||||||
timestamp: "",
|
timestamp: "",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
sender: .init(id: "", displayName: "Alice"),
|
sender: .init(id: "", displayName: "Alice"),
|
||||||
content: .init(body: "This is a message")), .single)
|
content: .init(body: "This is a message"))), groupStyle: .single))
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
|
|||||||
@@ -17,12 +17,14 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
import OrderedCollections
|
||||||
|
|
||||||
/// A table view cell that displays a timeline item in a room. The cell is intended
|
/// A table view cell that displays a timeline item in a room. The cell is intended
|
||||||
/// to be configured to display a SwiftUI view and not use any UIKit.
|
/// to be configured to display a SwiftUI view and not use any UIKit.
|
||||||
class TimelineItemCell: UITableViewCell {
|
class TimelineItemCell: UITableViewCell {
|
||||||
static let reuseIdentifier = "TimelineItemCell"
|
static let reuseIdentifier = "TimelineItemCell"
|
||||||
|
|
||||||
var item: RoomTimelineViewProvider?
|
var item: RoomTimelineItemViewModel?
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
item = nil
|
item = nil
|
||||||
@@ -38,7 +40,7 @@ class TimelineTableViewController: UIViewController {
|
|||||||
private let tableView = UITableView(frame: .zero, style: .plain)
|
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
|
|
||||||
var timelineStyle: TimelineStyle
|
var timelineStyle: TimelineStyle
|
||||||
var timelineItems: [RoomTimelineViewProvider] = [] {
|
var timelineItemsDictionary = OrderedDictionary<String, RoomTimelineItemViewModel>() {
|
||||||
didSet {
|
didSet {
|
||||||
guard !scrollAdapter.isScrolling.value else {
|
guard !scrollAdapter.isScrolling.value else {
|
||||||
// Delay updating until scrolling has stopped as programatic
|
// Delay updating until scrolling has stopped as programatic
|
||||||
@@ -46,15 +48,15 @@ class TimelineTableViewController: UIViewController {
|
|||||||
hasPendingUpdates = true
|
hasPendingUpdates = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
applySnapshot()
|
applySnapshot()
|
||||||
|
|
||||||
if timelineItems.isEmpty {
|
if timelineItemsDictionary.isEmpty {
|
||||||
paginateBackwardsPublisher.send()
|
paginateBackwardsPublisher.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The mode of the message composer. This is used to render selected
|
/// The mode of the message composer. This is used to render selected
|
||||||
/// items in the timeline when replying, editing etc.
|
/// items in the timeline when replying, editing etc.
|
||||||
var composerMode: RoomScreenComposerMode = .default
|
var composerMode: RoomScreenComposerMode = .default
|
||||||
@@ -73,9 +75,13 @@ class TimelineTableViewController: UIViewController {
|
|||||||
var contextMenuActionProvider: (@MainActor (_ itemId: String) -> TimelineItemMenuActions?)?
|
var contextMenuActionProvider: (@MainActor (_ itemId: String) -> TimelineItemMenuActions?)?
|
||||||
|
|
||||||
@Binding private var scrollToBottomButtonVisible: Bool
|
@Binding private var scrollToBottomButtonVisible: Bool
|
||||||
|
|
||||||
|
private var timelineItemsIDs: [String] {
|
||||||
|
timelineItemsDictionary.keys.elements
|
||||||
|
}
|
||||||
|
|
||||||
/// The table's diffable data source.
|
/// The table's diffable data source.
|
||||||
private var dataSource: UITableViewDiffableDataSource<TimelineSection, RoomTimelineViewProvider>?
|
private var dataSource: UITableViewDiffableDataSource<TimelineSection, String>?
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
/// The scroll view adapter used to detect whether scrolling is in progress.
|
/// The scroll view adapter used to detect whether scrolling is in progress.
|
||||||
@@ -183,24 +189,28 @@ class TimelineTableViewController: UIViewController {
|
|||||||
|
|
||||||
/// Configures a diffable data source for the timeline's table view.
|
/// Configures a diffable data source for the timeline's table view.
|
||||||
private func configureDataSource() {
|
private func configureDataSource() {
|
||||||
dataSource = .init(tableView: tableView) { [weak self] tableView, indexPath, timelineItem in
|
dataSource = .init(tableView: tableView) { [weak self] tableView, indexPath, id in
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TimelineItemCell.reuseIdentifier, for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: TimelineItemCell.reuseIdentifier, for: indexPath)
|
||||||
guard let self, let cell = cell as? TimelineItemCell else { return cell }
|
guard let self, let cell = cell as? TimelineItemCell else { return cell }
|
||||||
|
|
||||||
// A local reference to avoid capturing self in the cell configuration.
|
// A local reference to avoid capturing self in the cell configuration.
|
||||||
let coordinator = self.coordinator
|
let coordinator = self.coordinator
|
||||||
|
|
||||||
cell.item = timelineItem
|
let viewModel = timelineItemsDictionary[id]
|
||||||
|
cell.item = viewModel
|
||||||
|
guard let viewModel else {
|
||||||
|
return cell
|
||||||
|
}
|
||||||
cell.contentConfiguration = UIHostingConfiguration {
|
cell.contentConfiguration = UIHostingConfiguration {
|
||||||
timelineItem
|
RoomTimelineItemView(viewModel: viewModel)
|
||||||
.id(timelineItem.id)
|
.id(id)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu
|
.environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu
|
||||||
.onAppear {
|
.onAppear {
|
||||||
coordinator.send(viewAction: .itemAppeared(id: timelineItem.id))
|
coordinator.send(viewAction: .itemAppeared(id: id))
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
coordinator.send(viewAction: .itemDisappeared(id: timelineItem.id))
|
coordinator.send(viewAction: .itemDisappeared(id: id))
|
||||||
}
|
}
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
coordinator.send(viewAction: .linkClicked(url: url))
|
coordinator.send(viewAction: .linkClicked(url: url))
|
||||||
@@ -213,6 +223,8 @@ class TimelineTableViewController: UIViewController {
|
|||||||
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataSource?.defaultRowAnimation = .fade
|
||||||
|
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
}
|
}
|
||||||
@@ -226,13 +238,13 @@ class TimelineTableViewController: UIViewController {
|
|||||||
let previousLayout = layout()
|
let previousLayout = layout()
|
||||||
self.previousLayout = previousLayout
|
self.previousLayout = previousLayout
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, RoomTimelineViewProvider>()
|
var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, String>()
|
||||||
snapshot.appendSections([.main])
|
snapshot.appendSections([.main])
|
||||||
snapshot.appendItems(timelineItems)
|
snapshot.appendItems(timelineItemsIDs)
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
|
||||||
updateTopPadding()
|
updateTopPadding()
|
||||||
|
|
||||||
if previousLayout.isBottomVisible {
|
if previousLayout.isBottomVisible {
|
||||||
scrollToBottom(animated: false)
|
scrollToBottom(animated: false)
|
||||||
} else if let pinnedItem = previousLayout.pinnedItem {
|
} else if let pinnedItem = previousLayout.pinnedItem {
|
||||||
@@ -254,12 +266,12 @@ class TimelineTableViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard let bottomItemIndexPath = tableView.indexPathsForVisibleRows?.last,
|
guard let bottomItemIndexPath = tableView.indexPathsForVisibleRows?.last,
|
||||||
let bottomItem = dataSource.itemIdentifier(for: bottomItemIndexPath)
|
let bottomID = dataSource.itemIdentifier(for: bottomItemIndexPath)
|
||||||
else { return layout }
|
else { return layout }
|
||||||
|
|
||||||
let bottomCellFrame = tableView.cellFrame(for: bottomItem)
|
let bottomCellFrame = tableView.cellFrame(for: bottomID)
|
||||||
layout.pinnedItem = PinnedItem(id: bottomItem.id, position: .bottom, frame: bottomCellFrame)
|
layout.pinnedItem = PinnedItem(id: bottomID, position: .bottom, frame: bottomCellFrame)
|
||||||
layout.isBottomVisible = bottomItem == snapshot.itemIdentifiers.last
|
layout.isBottomVisible = bottomID == snapshot.itemIdentifiers.last
|
||||||
|
|
||||||
return layout
|
return layout
|
||||||
}
|
}
|
||||||
@@ -288,16 +300,16 @@ class TimelineTableViewController: UIViewController {
|
|||||||
|
|
||||||
/// Scrolls to the bottom of the timeline.
|
/// Scrolls to the bottom of the timeline.
|
||||||
private func scrollToBottom(animated: Bool) {
|
private func scrollToBottom(animated: Bool) {
|
||||||
guard let lastItem = timelineItems.last,
|
guard let lastItemID = timelineItemsIDs.last,
|
||||||
let lastIndexPath = dataSource?.indexPath(for: lastItem)
|
let lastIndexPath = dataSource?.indexPath(for: lastItemID)
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: animated)
|
tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restores the position of the timeline using the supplied item and snapshot.
|
/// Restores the position of the timeline using the supplied item and snapshot.
|
||||||
private func restoreScrollPosition(using pinnedItem: PinnedItem, and snapshot: NSDiffableDataSourceSnapshot<TimelineSection, RoomTimelineViewProvider>) {
|
private func restoreScrollPosition(using pinnedItem: PinnedItem, and snapshot: NSDiffableDataSourceSnapshot<TimelineSection, String>) {
|
||||||
guard let item = snapshot.itemIdentifiers.first(where: { $0.id == pinnedItem.id }),
|
guard let item = snapshot.itemIdentifiers.first(where: { $0 == pinnedItem.id }),
|
||||||
let indexPath = dataSource?.indexPath(for: item)
|
let indexPath = dataSource?.indexPath(for: item)
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
@@ -398,8 +410,8 @@ extension TimelineTableViewController {
|
|||||||
|
|
||||||
private extension UITableView {
|
private extension UITableView {
|
||||||
/// Returns the frame of the cell for a particular timeline item.
|
/// Returns the frame of the cell for a particular timeline item.
|
||||||
func cellFrame(for item: RoomTimelineViewProvider) -> CGRect? {
|
func cellFrame(for id: String) -> CGRect? {
|
||||||
guard let timelineCell = visibleCells.last(where: { ($0 as? TimelineItemCell)?.item == item }) else {
|
guard let timelineCell = visibleCells.last(where: { ($0 as? TimelineItemCell)?.item?.id == id }) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ struct TimelineView: UIViewControllerRepresentable {
|
|||||||
init(viewModelContext: RoomScreenViewModel.Context) {
|
init(viewModelContext: RoomScreenViewModel.Context) {
|
||||||
context = viewModelContext
|
context = viewModelContext
|
||||||
|
|
||||||
if viewModelContext.viewState.items.isEmpty {
|
if viewModelContext.viewState.itemViewModels.isEmpty {
|
||||||
viewModelContext.send(viewAction: .paginateBackwards)
|
viewModelContext.send(viewAction: .paginateBackwards)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,8 +56,8 @@ struct TimelineView: UIViewControllerRepresentable {
|
|||||||
if tableViewController.timelineStyle != timelineStyle {
|
if tableViewController.timelineStyle != timelineStyle {
|
||||||
tableViewController.timelineStyle = timelineStyle
|
tableViewController.timelineStyle = timelineStyle
|
||||||
}
|
}
|
||||||
if tableViewController.timelineItems != context.viewState.items {
|
if tableViewController.timelineItemsDictionary != context.viewState.itemsDictionary {
|
||||||
tableViewController.timelineItems = context.viewState.items
|
tableViewController.timelineItemsDictionary = context.viewState.itemsDictionary
|
||||||
}
|
}
|
||||||
if tableViewController.canBackPaginate != context.viewState.canBackPaginate {
|
if tableViewController.canBackPaginate != context.viewState.canBackPaginate {
|
||||||
tableViewController.canBackPaginate = context.viewState.canBackPaginate
|
tableViewController.canBackPaginate = context.viewState.canBackPaginate
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RoomTimelineItemView: View {
|
||||||
|
@ObservedObject var viewModel: RoomTimelineItemViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
timelineView
|
||||||
|
.environment(\.timelineGroupStyle, viewModel.groupStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder private var timelineView: some View {
|
||||||
|
switch viewModel.type {
|
||||||
|
case .text(let item):
|
||||||
|
TextRoomTimelineView(timelineItem: item)
|
||||||
|
case .separator(let item):
|
||||||
|
SeparatorRoomTimelineView(timelineItem: item)
|
||||||
|
case .image(let item):
|
||||||
|
ImageRoomTimelineView(timelineItem: item)
|
||||||
|
case .video(let item):
|
||||||
|
VideoRoomTimelineView(timelineItem: item)
|
||||||
|
case .audio(let item):
|
||||||
|
AudioRoomTimelineView(timelineItem: item)
|
||||||
|
case .file(let item):
|
||||||
|
FileRoomTimelineView(timelineItem: item)
|
||||||
|
case .emote(let item):
|
||||||
|
EmoteRoomTimelineView(timelineItem: item)
|
||||||
|
case .notice(let item):
|
||||||
|
NoticeRoomTimelineView(timelineItem: item)
|
||||||
|
case .redacted(let item):
|
||||||
|
RedactedRoomTimelineView(timelineItem: item)
|
||||||
|
case .encrypted(let item):
|
||||||
|
EncryptedRoomTimelineView(timelineItem: item)
|
||||||
|
case .readMarker(let item):
|
||||||
|
ReadMarkerRoomTimelineView(timelineItem: item)
|
||||||
|
case .paginationIndicator(let item):
|
||||||
|
PaginationIndicatorRoomTimelineView(timelineItem: item)
|
||||||
|
case .sticker(let item):
|
||||||
|
StickerRoomTimelineView(timelineItem: item)
|
||||||
|
case .unsupported(let item):
|
||||||
|
UnsupportedRoomTimelineView(timelineItem: item)
|
||||||
|
case .timelineStart(let item):
|
||||||
|
TimelineStartRoomTimelineView(timelineItem: item)
|
||||||
|
case .state(let item):
|
||||||
|
StateRoomTimelineView(timelineItem: item)
|
||||||
|
case .group(let item):
|
||||||
|
CollapsibleRoomTimelineView(timelineItem: item)
|
||||||
|
case .location(let item):
|
||||||
|
LocationRoomTimelineView(timelineItem: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timelineGroupStyle: TimelineGroupStyle {
|
||||||
|
viewModel.groupStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class RoomTimelineItemViewModel: Identifiable, Equatable, ObservableObject {
|
||||||
|
static func == (lhs: RoomTimelineItemViewModel, rhs: RoomTimelineItemViewModel) -> Bool {
|
||||||
|
lhs.type == rhs.type && lhs.groupStyle == rhs.groupStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
@Published var type: RoomTimelineItemType
|
||||||
|
@Published var groupStyle: TimelineGroupStyle
|
||||||
|
|
||||||
|
var id: String {
|
||||||
|
type.id
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) {
|
||||||
|
self.init(type: .init(item: item), groupStyle: groupStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(type: RoomTimelineItemType, groupStyle: TimelineGroupStyle) {
|
||||||
|
self.type = type
|
||||||
|
self.groupStyle = groupStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
var isReactable: Bool {
|
||||||
|
type.isReactable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RoomTimelineItemType: Equatable {
|
||||||
|
case text(TextRoomTimelineItem)
|
||||||
|
case separator(SeparatorRoomTimelineItem)
|
||||||
|
case image(ImageRoomTimelineItem)
|
||||||
|
case video(VideoRoomTimelineItem)
|
||||||
|
case audio(AudioRoomTimelineItem)
|
||||||
|
case file(FileRoomTimelineItem)
|
||||||
|
case emote(EmoteRoomTimelineItem)
|
||||||
|
case notice(NoticeRoomTimelineItem)
|
||||||
|
case redacted(RedactedRoomTimelineItem)
|
||||||
|
case encrypted(EncryptedRoomTimelineItem)
|
||||||
|
case readMarker(ReadMarkerRoomTimelineItem)
|
||||||
|
case paginationIndicator(PaginationIndicatorRoomTimelineItem)
|
||||||
|
case sticker(StickerRoomTimelineItem)
|
||||||
|
case unsupported(UnsupportedRoomTimelineItem)
|
||||||
|
case timelineStart(TimelineStartRoomTimelineItem)
|
||||||
|
case state(StateRoomTimelineItem)
|
||||||
|
case group(CollapsibleTimelineItem)
|
||||||
|
case location(LocationRoomTimelineItem)
|
||||||
|
|
||||||
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
|
init(item: RoomTimelineItemProtocol) {
|
||||||
|
switch item {
|
||||||
|
case let item as TextRoomTimelineItem:
|
||||||
|
self = .text(item)
|
||||||
|
case let item as ImageRoomTimelineItem:
|
||||||
|
self = .image(item)
|
||||||
|
case let item as VideoRoomTimelineItem:
|
||||||
|
self = .video(item)
|
||||||
|
case let item as AudioRoomTimelineItem:
|
||||||
|
self = .audio(item)
|
||||||
|
case let item as FileRoomTimelineItem:
|
||||||
|
self = .file(item)
|
||||||
|
case let item as SeparatorRoomTimelineItem:
|
||||||
|
self = .separator(item)
|
||||||
|
case let item as NoticeRoomTimelineItem:
|
||||||
|
self = .notice(item)
|
||||||
|
case let item as EmoteRoomTimelineItem:
|
||||||
|
self = .emote(item)
|
||||||
|
case let item as RedactedRoomTimelineItem:
|
||||||
|
self = .redacted(item)
|
||||||
|
case let item as EncryptedRoomTimelineItem:
|
||||||
|
self = .encrypted(item)
|
||||||
|
case let item as ReadMarkerRoomTimelineItem:
|
||||||
|
self = .readMarker(item)
|
||||||
|
case let item as PaginationIndicatorRoomTimelineItem:
|
||||||
|
self = .paginationIndicator(item)
|
||||||
|
case let item as StickerRoomTimelineItem:
|
||||||
|
self = .sticker(item)
|
||||||
|
case let item as UnsupportedRoomTimelineItem:
|
||||||
|
self = .unsupported(item)
|
||||||
|
case let item as TimelineStartRoomTimelineItem:
|
||||||
|
self = .timelineStart(item)
|
||||||
|
case let item as StateRoomTimelineItem:
|
||||||
|
self = .state(item)
|
||||||
|
case let item as CollapsibleTimelineItem:
|
||||||
|
self = .group(item)
|
||||||
|
case let item as LocationRoomTimelineItem:
|
||||||
|
self = .location(item)
|
||||||
|
default:
|
||||||
|
fatalError("Unknown timeline item")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var id: String {
|
||||||
|
switch self {
|
||||||
|
case .text(let item as RoomTimelineItemProtocol),
|
||||||
|
.separator(let item as RoomTimelineItemProtocol),
|
||||||
|
.image(let item as RoomTimelineItemProtocol),
|
||||||
|
.video(let item as RoomTimelineItemProtocol),
|
||||||
|
.audio(let item as RoomTimelineItemProtocol),
|
||||||
|
.file(let item as RoomTimelineItemProtocol),
|
||||||
|
.emote(let item as RoomTimelineItemProtocol),
|
||||||
|
.notice(let item as RoomTimelineItemProtocol),
|
||||||
|
.redacted(let item as RoomTimelineItemProtocol),
|
||||||
|
.encrypted(let item as RoomTimelineItemProtocol),
|
||||||
|
.readMarker(let item as RoomTimelineItemProtocol),
|
||||||
|
.paginationIndicator(let item as RoomTimelineItemProtocol),
|
||||||
|
.sticker(let item as RoomTimelineItemProtocol),
|
||||||
|
.unsupported(let item as RoomTimelineItemProtocol),
|
||||||
|
.timelineStart(let item as RoomTimelineItemProtocol),
|
||||||
|
.state(let item as RoomTimelineItemProtocol),
|
||||||
|
.group(let item as RoomTimelineItemProtocol),
|
||||||
|
.location(let item as RoomTimelineItemProtocol):
|
||||||
|
return item.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether or not it is possible to send a reaction to this timeline item.
|
||||||
|
var isReactable: Bool {
|
||||||
|
switch self {
|
||||||
|
case .text, .image, .video, .audio, .file, .emote, .notice, .sticker, .location:
|
||||||
|
return true
|
||||||
|
case .redacted, .encrypted, .unsupported, .state: // Event based items that aren't reactable
|
||||||
|
return false
|
||||||
|
case .timelineStart, .separator, .readMarker, .paginationIndicator: // Virtual items are never reactable
|
||||||
|
return false
|
||||||
|
case .group:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2022 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
enum RoomTimelineViewProvider: Identifiable, Hashable {
|
|
||||||
case text(TextRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case separator(SeparatorRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case image(ImageRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case video(VideoRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case audio(AudioRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case file(FileRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case emote(EmoteRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case notice(NoticeRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case redacted(RedactedRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case encrypted(EncryptedRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case readMarker(ReadMarkerRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case paginationIndicator(PaginationIndicatorRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case sticker(StickerRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case unsupported(UnsupportedRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case timelineStart(TimelineStartRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case state(StateRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
case group(CollapsibleTimelineItem, TimelineGroupStyle)
|
|
||||||
case location(LocationRoomTimelineItem, TimelineGroupStyle)
|
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity
|
|
||||||
init(timelineItem: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) {
|
|
||||||
switch timelineItem {
|
|
||||||
case let item as TextRoomTimelineItem:
|
|
||||||
self = .text(item, groupStyle)
|
|
||||||
case let item as ImageRoomTimelineItem:
|
|
||||||
self = .image(item, groupStyle)
|
|
||||||
case let item as VideoRoomTimelineItem:
|
|
||||||
self = .video(item, groupStyle)
|
|
||||||
case let item as AudioRoomTimelineItem:
|
|
||||||
self = .audio(item, groupStyle)
|
|
||||||
case let item as FileRoomTimelineItem:
|
|
||||||
self = .file(item, groupStyle)
|
|
||||||
case let item as SeparatorRoomTimelineItem:
|
|
||||||
self = .separator(item, groupStyle)
|
|
||||||
case let item as NoticeRoomTimelineItem:
|
|
||||||
self = .notice(item, groupStyle)
|
|
||||||
case let item as EmoteRoomTimelineItem:
|
|
||||||
self = .emote(item, groupStyle)
|
|
||||||
case let item as RedactedRoomTimelineItem:
|
|
||||||
self = .redacted(item, groupStyle)
|
|
||||||
case let item as EncryptedRoomTimelineItem:
|
|
||||||
self = .encrypted(item, groupStyle)
|
|
||||||
case let item as ReadMarkerRoomTimelineItem:
|
|
||||||
self = .readMarker(item, groupStyle)
|
|
||||||
case let item as PaginationIndicatorRoomTimelineItem:
|
|
||||||
self = .paginationIndicator(item, groupStyle)
|
|
||||||
case let item as StickerRoomTimelineItem:
|
|
||||||
self = .sticker(item, groupStyle)
|
|
||||||
case let item as UnsupportedRoomTimelineItem:
|
|
||||||
self = .unsupported(item, groupStyle)
|
|
||||||
case let item as TimelineStartRoomTimelineItem:
|
|
||||||
self = .timelineStart(item, groupStyle)
|
|
||||||
case let item as StateRoomTimelineItem:
|
|
||||||
self = .state(item, groupStyle)
|
|
||||||
case let item as CollapsibleTimelineItem:
|
|
||||||
self = .group(item, groupStyle)
|
|
||||||
case let item as LocationRoomTimelineItem:
|
|
||||||
self = .location(item, groupStyle)
|
|
||||||
default:
|
|
||||||
fatalError("Unknown timeline item")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var id: String {
|
|
||||||
switch self {
|
|
||||||
case .text(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.separator(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.image(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.video(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.audio(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.file(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.emote(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.notice(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.redacted(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.encrypted(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.readMarker(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.paginationIndicator(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.sticker(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.unsupported(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.timelineStart(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.state(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.group(let item as RoomTimelineItemProtocol, _),
|
|
||||||
.location(let item as RoomTimelineItemProtocol, _):
|
|
||||||
return item.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether or not it is possible to send a reaction to this timeline item.
|
|
||||||
var isReactable: Bool {
|
|
||||||
switch self {
|
|
||||||
case .text, .image, .video, .audio, .file, .emote, .notice, .sticker, .location:
|
|
||||||
return true
|
|
||||||
case .redacted, .encrypted, .unsupported, .state: // Event based items that aren't reactable
|
|
||||||
return false
|
|
||||||
case .timelineStart, .separator, .readMarker, .paginationIndicator: // Virtual items are never reactable
|
|
||||||
return false
|
|
||||||
case .group:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RoomTimelineViewProvider: View {
|
|
||||||
var body: some View {
|
|
||||||
timelineView
|
|
||||||
.environment(\.timelineGroupStyle, timelineGroupStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder private var timelineView: some View {
|
|
||||||
switch self {
|
|
||||||
case .text(let item, _):
|
|
||||||
TextRoomTimelineView(timelineItem: item)
|
|
||||||
case .separator(let item, _):
|
|
||||||
SeparatorRoomTimelineView(timelineItem: item)
|
|
||||||
case .image(let item, _):
|
|
||||||
ImageRoomTimelineView(timelineItem: item)
|
|
||||||
case .video(let item, _):
|
|
||||||
VideoRoomTimelineView(timelineItem: item)
|
|
||||||
case .audio(let item, _):
|
|
||||||
AudioRoomTimelineView(timelineItem: item)
|
|
||||||
case .file(let item, _):
|
|
||||||
FileRoomTimelineView(timelineItem: item)
|
|
||||||
case .emote(let item, _):
|
|
||||||
EmoteRoomTimelineView(timelineItem: item)
|
|
||||||
case .notice(let item, _):
|
|
||||||
NoticeRoomTimelineView(timelineItem: item)
|
|
||||||
case .redacted(let item, _):
|
|
||||||
RedactedRoomTimelineView(timelineItem: item)
|
|
||||||
case .encrypted(let item, _):
|
|
||||||
EncryptedRoomTimelineView(timelineItem: item)
|
|
||||||
case .readMarker(let item, _):
|
|
||||||
ReadMarkerRoomTimelineView(timelineItem: item)
|
|
||||||
case .paginationIndicator(let item, _):
|
|
||||||
PaginationIndicatorRoomTimelineView(timelineItem: item)
|
|
||||||
case .sticker(let item, _):
|
|
||||||
StickerRoomTimelineView(timelineItem: item)
|
|
||||||
case .unsupported(let item, _):
|
|
||||||
UnsupportedRoomTimelineView(timelineItem: item)
|
|
||||||
case .timelineStart(let item, _):
|
|
||||||
TimelineStartRoomTimelineView(timelineItem: item)
|
|
||||||
case .state(let item, _):
|
|
||||||
StateRoomTimelineView(timelineItem: item)
|
|
||||||
case .group(let item, _):
|
|
||||||
CollapsibleRoomTimelineView(timelineItem: item)
|
|
||||||
case .location(let item, _):
|
|
||||||
LocationRoomTimelineView(timelineItem: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var timelineGroupStyle: TimelineGroupStyle {
|
|
||||||
switch self {
|
|
||||||
case .text(_, let groupStyle),
|
|
||||||
.separator(_, let groupStyle),
|
|
||||||
.image(_, let groupStyle),
|
|
||||||
.video(_, let groupStyle),
|
|
||||||
.audio(_, let groupStyle),
|
|
||||||
.file(_, let groupStyle),
|
|
||||||
.emote(_, let groupStyle),
|
|
||||||
.notice(_, let groupStyle),
|
|
||||||
.redacted(_, let groupStyle),
|
|
||||||
.encrypted(_, let groupStyle),
|
|
||||||
.readMarker(_, let groupStyle),
|
|
||||||
.paginationIndicator(_, let groupStyle),
|
|
||||||
.sticker(_, let groupStyle),
|
|
||||||
.unsupported(_, let groupStyle),
|
|
||||||
.timelineStart(_, let groupStyle),
|
|
||||||
.state(_, let groupStyle),
|
|
||||||
.group(_, let groupStyle),
|
|
||||||
.location(_, let groupStyle):
|
|
||||||
return groupStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,9 +51,9 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Then the messages should be grouped together.
|
// Then the messages should be grouped together.
|
||||||
XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.items[1].timelineGroupStyle, .middle, "Nothing should prevent the middle message from being grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[1].groupStyle, .middle, "Nothing should prevent the middle message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.items[2].timelineGroupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageGroupingMultipleSenders() {
|
func testMessageGroupingMultipleSenders() {
|
||||||
@@ -84,12 +84,12 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Then the messages should be grouped by sender.
|
// Then the messages should be grouped by sender.
|
||||||
XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .single, "A message should not be grouped when the sender changes.")
|
XCTAssertEqual(viewModel.state.itemViewModels[0].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||||
XCTAssertEqual(viewModel.state.items[1].timelineGroupStyle, .single, "A message should not be grouped when the sender changes.")
|
XCTAssertEqual(viewModel.state.itemViewModels[1].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||||
XCTAssertEqual(viewModel.state.items[2].timelineGroupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
XCTAssertEqual(viewModel.state.itemViewModels[2].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||||
XCTAssertEqual(viewModel.state.items[3].timelineGroupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
XCTAssertEqual(viewModel.state.itemViewModels[3].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||||
XCTAssertEqual(viewModel.state.items[4].timelineGroupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
XCTAssertEqual(viewModel.state.itemViewModels[4].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||||
XCTAssertEqual(viewModel.state.items[5].timelineGroupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
XCTAssertEqual(viewModel.state.itemViewModels[5].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageGroupingWithLeadingReactions() {
|
func testMessageGroupingWithLeadingReactions() {
|
||||||
@@ -115,9 +115,9 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Then the first message should not be grouped but the other two should.
|
// Then the first message should not be grouped but the other two should.
|
||||||
XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .single, "When the first message has reactions it should not be grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[0].groupStyle, .single, "When the first message has reactions it should not be grouped.")
|
||||||
XCTAssertEqual(viewModel.state.items[1].timelineGroupStyle, .first, "A new group should be made when the preceding message has reactions.")
|
XCTAssertEqual(viewModel.state.itemViewModels[1].groupStyle, .first, "A new group should be made when the preceding message has reactions.")
|
||||||
XCTAssertEqual(viewModel.state.items[2].timelineGroupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageGroupingWithInnerReactions() {
|
func testMessageGroupingWithInnerReactions() {
|
||||||
@@ -143,9 +143,9 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Then the first and second messages should be grouped and the last one should not.
|
// Then the first and second messages should be grouped and the last one should not.
|
||||||
XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.items[1].timelineGroupStyle, .last, "When the message has reactions, the group should end here.")
|
XCTAssertEqual(viewModel.state.itemViewModels[1].groupStyle, .last, "When the message has reactions, the group should end here.")
|
||||||
XCTAssertEqual(viewModel.state.items[2].timelineGroupStyle, .single, "The last message should not be grouped when the preceding message has reactions.")
|
XCTAssertEqual(viewModel.state.itemViewModels[2].groupStyle, .single, "The last message should not be grouped when the preceding message has reactions.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageGroupingWithTrailingReactions() {
|
func testMessageGroupingWithTrailingReactions() {
|
||||||
@@ -171,9 +171,9 @@ class RoomScreenViewModelTests: XCTestCase {
|
|||||||
userIndicatorController: userIndicatorControllerMock)
|
userIndicatorController: userIndicatorControllerMock)
|
||||||
|
|
||||||
// Then the messages should be grouped together.
|
// Then the messages should be grouped together.
|
||||||
XCTAssertEqual(viewModel.state.items[0].timelineGroupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.items[1].timelineGroupStyle, .middle, "Nothing should prevent the second message from being grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[1].groupStyle, .middle, "Nothing should prevent the second message from being grouped.")
|
||||||
XCTAssertEqual(viewModel.state.items[2].timelineGroupStyle, .last, "Reactions on the last message should not prevent it from being grouped.")
|
XCTAssertEqual(viewModel.state.itemViewModels[2].groupStyle, .last, "Reactions on the last message should not prevent it from being grouped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGoToUserDetailsSuccessNoDelay() async {
|
func testGoToUserDetailsSuccessNoDelay() async {
|
||||||
|
|||||||
Reference in New Issue
Block a user