Read Receipts display with the user avatar (#1038)

* added the read receipt into the code

* fixed some things lost during merge

* implemented the status view to be always at the trailing side

* preview code for better testing

* removed timelineStyle modifier

* removed also the group style

* added some UI tests for the read receipts
This commit is contained in:
Mauro
2023-06-08 12:05:56 +02:00
committed by GitHub
parent c7ee69d75d
commit 75a3751f78
38 changed files with 360 additions and 65 deletions
+19 -11
View File
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -99,7 +99,6 @@
281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; };
282A5F3375DDC774AE09B0C3 /* TracingConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */; };
2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7310D8DFE01AF45F0689C3AA /* Publisher.swift */; };
28A94A7DF684F2A29C4ADFE9 /* TimelineReceiptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5457B0320D028A676C248F /* TimelineReceiptView.swift */; };
290FDB0FFDC2F1DDF660343E /* TestMeasurementParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4048041C1A6B20CB97FD18 /* TestMeasurementParser.swift */; };
292827744227DF61C930BDDB /* CreateRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */; };
2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024F7398C5FC12586FB10E9D /* EffectsScene.swift */; };
@@ -175,6 +174,7 @@
48FE5F0E3921146DBF4E61E7 /* OnboardingBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06A27D9C70E0DCC1E199163 /* OnboardingBackgroundView.swift */; };
492274DA6691EE985C2FCCAA /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
496CC9D59ACFAB84FD9B3B5F /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840E86A67DB2C92C09771EAD /* AnalyticsPromptScreenModels.swift */; };
49814A48470F347426513B07 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */; };
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; };
4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; };
4A85928E27D4C1A548A06EE9 /* StartChatScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */; };
@@ -360,6 +360,7 @@
8F2FAA98457750D9D664136F /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; };
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
92B95779840CD749117B3615 /* EmojiMartStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38AE3617D7619EF30CDD229 /* EmojiMartStore.swift */; };
92D9088B901CEBB1A99ECA4E /* RoomMemberProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */; };
@@ -656,6 +657,7 @@
FC10228E73323BDC09526F97 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 2B43F2AF7456567FE37270A7 /* KeychainAccess */; };
FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */; };
FDFB75880BC21B297E66AB28 /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A78A43F3971B1BE8CF642E /* TimelineItemStatusView.swift */; };
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1593DD87F974F8509BB619 /* ElementAnimations.swift */; };
FF149F0A3550A54C50ECBE7A /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843CF833BF6485B64AC87E1 /* AppRouter.swift */; };
FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */; };
@@ -761,7 +763,7 @@
1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
127A57D053CE8C87B5EFB089 /* Consumable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = "<group>"; };
127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.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>"; };
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>"; };
@@ -774,6 +776,7 @@
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = "<group>"; };
1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfigurationTests.swift; sourceTree = "<group>"; };
184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = "<group>"; };
1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReadReceiptsView.swift; sourceTree = "<group>"; };
18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxy.swift; sourceTree = "<group>"; };
18FE0CDF1FFA92EA7EE17B0B /* RoomTimelineControllerFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactoryProtocol.swift; sourceTree = "<group>"; };
1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineView.swift; sourceTree = "<group>"; };
@@ -825,6 +828,7 @@
2F36C5D9B37E50915ECBD3EE /* RoomMemberProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxy.swift; sourceTree = "<group>"; };
303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModel.swift; sourceTree = "<group>"; };
30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerTests.swift; sourceTree = "<group>"; };
314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceipt.swift; sourceTree = "<group>"; };
31B35311C7FED04B0E1B80C2 /* RoomMemberDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetails.swift; sourceTree = "<group>"; };
31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = "<group>"; };
32B5E17028C02DFA7DDA3931 /* RoomMemberProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyProtocol.swift; sourceTree = "<group>"; };
@@ -845,7 +849,6 @@
3948D16F021DFDB2CD26EAA8 /* MockBackgroundTaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBackgroundTaskService.swift; sourceTree = "<group>"; };
398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableFrameModifier.swift; sourceTree = "<group>"; };
39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenUITests.swift; sourceTree = "<group>"; };
3A5457B0320D028A676C248F /* TimelineReceiptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReceiptView.swift; sourceTree = "<group>"; };
3B5E97E9615A158C76B2AB77 /* DateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTests.swift; sourceTree = "<group>"; };
3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenUITests.swift; sourceTree = "<group>"; };
3C1A3D524D63815B28FA4D62 /* EmojiCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCategory.swift; sourceTree = "<group>"; };
@@ -880,8 +883,9 @@
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>"; };
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>"; };
47A78A43F3971B1BE8CF642E /* TimelineItemStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemStatusView.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>"; };
4959CECEC984B3995616F427 /* DataProtectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProtectionManager.swift; sourceTree = "<group>"; };
@@ -1033,7 +1037,7 @@
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>"; };
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>"; };
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
8F61A0DD8243B395499C99A2 /* InvitesScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenUITests.swift; sourceTree = "<group>"; };
@@ -1132,7 +1136,7 @@
B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.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>"; };
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>"; };
B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsSignalling.swift; sourceTree = "<group>"; };
@@ -1196,7 +1200,7 @@
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>"; };
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>"; };
D06A27D9C70E0DCC1E199163 /* OnboardingBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackgroundView.swift; sourceTree = "<group>"; };
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
@@ -1262,7 +1266,7 @@
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>"; };
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>"; };
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>"; };
@@ -2158,6 +2162,7 @@
children = (
B858A61F2A570DFB8DE570A7 /* AggregratedReaction.swift */,
96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */,
314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */,
5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */,
BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */,
);
@@ -2964,7 +2969,8 @@
children = (
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */,
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */,
3A5457B0320D028A676C248F /* TimelineReceiptView.swift */,
1877038D1AD3D5A029F8AE2C /* TimelineReadReceiptsView.swift */,
47A78A43F3971B1BE8CF642E /* TimelineItemStatusView.swift */,
);
path = Supplementary;
sourceTree = "<group>";
@@ -4005,6 +4011,7 @@
2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */,
743790BF6A5B0577EA74AF14 /* ReadMarkerRoomTimelineItem.swift in Sources */,
8EF63DDDC1B54F122070B04D /* ReadMarkerRoomTimelineView.swift in Sources */,
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */,
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */,
AF19D65A9C60C6B2646F3210 /* RedactedRoomTimelineItem.swift in Sources */,
13853973A5E24374FCEDE8A3 /* RedactedRoomTimelineView.swift in Sources */,
@@ -4147,11 +4154,12 @@
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */,
1B88BB631F7FC45A213BB554 /* TimelineItemSender.swift in Sources */,
9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */,
28A94A7DF684F2A29C4ADFE9 /* TimelineReceiptView.swift in Sources */,
49814A48470F347426513B07 /* TimelineReadReceiptsView.swift in Sources */,
2A90DD14DE5C891BFA433950 /* TimelineReplyView.swift in Sources */,
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */,
C4FE0E11A907C8999F92D5A8 /* TimelineStartRoomTimelineItem.swift in Sources */,
6FF51EB400DBA0668FC38B97 /* TimelineStartRoomTimelineView.swift in Sources */,
FDFB75880BC21B297E66AB28 /* TimelineItemStatusView.swift in Sources */,
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */,
FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */,
702694459B649B9D3A3C34F8 /* TimelineTableViewController.swift in Sources */,
@@ -165,8 +165,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-parsing",
"state" : {
"revision" : "c6e2241daa46e5c6e5027a93b161bca6ba692bcc",
"version" : "0.12.0"
"revision" : "27c941bbd22a4bbc53005a15a0440443fd892f70",
"version" : "0.12.1"
}
},
{
+3
View File
@@ -49,9 +49,12 @@ enum UserAvatarSizeOnScreen {
case startChat
case memberDetails
case inviteUsers
case readReceipt
var value: CGFloat {
switch self {
case .readReceipt:
return 16
case .timeline:
return 32
case .home:
@@ -77,10 +77,12 @@ struct RoomScreenViewState: BindableState {
var roomTitle = ""
var roomAvatarURL: URL?
var items: [RoomTimelineViewProvider] = []
var members: [String: RoomMemberState] = [:]
var canBackPaginate = true
var isBackPaginating = false
var showLoading = false
var timelineStyle: TimelineStyle
var readReceiptsEnabled: Bool
var bindings: RoomScreenViewStateBindings
@@ -123,3 +125,8 @@ enum RoomScreenErrorType: Hashable {
/// A specific error message shown in a toast.
case toast(String)
}
struct RoomMemberState {
let displayName: String?
let avatarURL: URL?
}
@@ -44,6 +44,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
roomTitle: roomProxy.roomTitle,
roomAvatarURL: roomProxy.avatarURL,
timelineStyle: ServiceLocator.shared.settings.timelineStyle,
readReceiptsEnabled: ServiceLocator.shared.settings.readReceiptsEnabled,
bindings: .init(composerText: "", composerFocused: false)),
imageProvider: mediaProvider)
@@ -88,6 +89,19 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
ServiceLocator.shared.settings.$timelineStyle
.weakAssign(to: \.state.timelineStyle, on: self)
.store(in: &cancellables)
ServiceLocator.shared.settings.$readReceiptsEnabled
.weakAssign(to: \.state.readReceiptsEnabled, on: self)
.store(in: &cancellables)
roomProxy.membersPublisher
.map { members in
members.reduce(into: [String: RoomMemberState]()) { dictionary, member in
dictionary[member.userID] = RoomMemberState(displayName: member.displayName, avatarURL: member.avatarURL)
}
}
.weakAssign(to: \.state.members, on: self)
.store(in: &cancellables)
buildTimelineViews()
}
@@ -68,7 +68,8 @@ struct RoomScreen: View {
TimelineView()
.id(context.viewState.roomId)
.environmentObject(context)
.timelineStyle(context.viewState.timelineStyle)
.environment(\.timelineStyle, context.viewState.timelineStyle)
.environment(\.readReceiptsEnabled, context.viewState.readReceiptsEnabled)
.overlay(alignment: .bottomTrailing) { scrollToBottomButton }
}
@@ -42,16 +42,28 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
header
.zIndex(1)
}
HStack {
if timelineItem.isOutgoing {
Spacer()
VStack(alignment: alignment, spacing: 0) {
HStack {
if timelineItem.isOutgoing {
Spacer()
}
messageBubbleWithReactions
}
.padding(timelineItem.isOutgoing ? .leading : .trailing, 40) // Extra padding to differentiate alignment.
messageBubbleWithReactions
HStack(spacing: 0) {
if !timelineItem.isOutgoing {
Spacer()
}
TimelineItemStatusView(timelineItem: timelineItem)
.environmentObject(context)
.padding(.top, 8)
.padding(.bottom, 3)
}
}
.padding(.horizontal, 16.0)
.padding(timelineItem.isOutgoing ? .leading : .trailing, 40) // Extra padding to differentiate alignment.
.padding(.horizontal, 16)
}
}
}
@@ -92,11 +104,6 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
context.send(viewAction: .sendReaction(key: key, eventID: timelineItem.id))
}
}
TimelineReceiptView(timelineItem: timelineItem)
.environmentObject(context)
.padding(.top, 10)
.padding(.bottom, 3)
}
}
@@ -120,7 +127,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
}
.padding(.top, messageBubbleTopPadding)
}
@ViewBuilder
var styledContent: some View {
if shouldAvoidBubbling {
@@ -254,6 +261,9 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
static var previews: some View {
mockTimeline
.previewDisplayName("Mock Timeline")
mockTimeline
.environment(\.readReceiptsEnabled, true)
.previewDisplayName("Mock Timeline with read receipts")
mockTimeline
.environment(\.layoutDirection, .rightToLeft)
.previewDisplayName("Mock Timeline RTL")
@@ -267,7 +277,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
item.padding(TimelineStyle.bubbles.rowInsets) // Insets added in the table view cells
}
}
.timelineStyle(.bubbles)
.environment(\.timelineStyle, .bubbles)
.previewLayout(.sizeThatFits)
.environmentObject(viewModel.context)
}
@@ -42,7 +42,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
supplementaryViews
}
}
TimelineReceiptView(timelineItem: timelineItem)
TimelineItemStatusView(timelineItem: timelineItem)
.environmentObject(context)
}
}
@@ -139,7 +139,7 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider {
.padding(TimelineStyle.plain.rowInsets) // Insets added in the table view cells
}
}
.timelineStyle(.plain)
.environment(\.timelineStyle, .plain)
.previewLayout(.sizeThatFits)
.environmentObject(viewModel.context)
}
@@ -58,6 +58,10 @@ private struct TimelineGroupStyleKey: EnvironmentKey {
static let defaultValue = TimelineGroupStyle.single
}
private struct ReadReceiptsEnabledKey: EnvironmentKey {
static let defaultValue = false
}
extension EnvironmentValues {
var timelineStyle: TimelineStyle {
get { self[TimelineStyleKey.self] }
@@ -68,14 +72,9 @@ extension EnvironmentValues {
get { self[TimelineGroupStyleKey.self] }
set { self[TimelineGroupStyleKey.self] = newValue }
}
}
extension View {
func timelineStyle(_ style: TimelineStyle) -> some View {
environment(\.timelineStyle, style)
}
func timelineGroupStyle(_ groupStyle: TimelineGroupStyle) -> some View {
environment(\.timelineGroupStyle, groupStyle)
var readReceiptsEnabled: Bool {
get { self[ReadReceiptsEnabledKey.self] }
set { self[ReadReceiptsEnabledKey.self] = newValue }
}
}
@@ -16,10 +16,10 @@
import SwiftUI
// This is also going to be used for read receipts in the future
struct TimelineReceiptView: View {
struct TimelineItemStatusView: View {
let timelineItem: EventBasedTimelineItemProtocol
@Environment(\.timelineStyle) private var style
@Environment(\.readReceiptsEnabled) private var readReceiptsEnabled
@EnvironmentObject private var context: RoomScreenViewModel.Context
@State private var shouldShowDeliveryStatus = true
@@ -34,7 +34,9 @@ struct TimelineReceiptView: View {
}
var body: some View {
if shouldShowDeliveryStatus {
if !timelineItem.properties.orderedReadReceipts.isEmpty, readReceiptsEnabled {
readReceipts
} else if shouldShowDeliveryStatus {
deliveryStatus
.onChange(of: timelineItem.properties.deliveryStatus) { newValue in
if newValue == .sent, !isLast {
@@ -71,4 +73,9 @@ struct TimelineReceiptView: View {
// The bubbles handle the failure internally
}
}
var readReceipts: some View {
TimelineReadReceiptsView(timelineItem: timelineItem)
.environmentObject(context)
}
}
@@ -0,0 +1,97 @@
//
// 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 SwiftUI
struct TimelineReadReceiptsView: View {
let displayNumber = 3
let timelineItem: EventBasedTimelineItemProtocol
@EnvironmentObject private var context: RoomScreenViewModel.Context
var body: some View {
HStack(spacing: 2) {
HStack(spacing: -4) {
let receiptsToDisplay = timelineItem.properties.orderedReadReceipts.prefix(displayNumber)
ForEach(0..<receiptsToDisplay.count, id: \.self) { index in
let receipt = receiptsToDisplay[index]
LoadableAvatarImage(url: context.viewState.members[receipt.userID]?.avatarURL,
name: context.viewState.members[receipt.userID]?.displayName,
contentID: receipt.userID,
avatarSize: .user(on: .readReceipt),
imageProvider: context.imageProvider)
.overlay(
RoundedRectangle(cornerRadius: .infinity)
.stroke(Color.element.background, lineWidth: 1)
)
.zIndex(Double(displayNumber - index))
}
}
if timelineItem.properties.orderedReadReceipts.count > displayNumber {
let remaining = timelineItem.properties.orderedReadReceipts.count - displayNumber
Text("+\(remaining)")
.font(.compound.bodySM)
.foregroundColor(.element.primaryContent)
}
}
}
}
struct TimelineReadReceiptsView_Previews: PreviewProvider {
static let members: [RoomMemberProxyMock] = [
.mockAlice,
.mockBob,
.mockCharlie,
.mockDan,
.mockMe
]
static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(),
roomProxy: RoomProxyMock(with: .init(displayName: "Test",
members: members)))
static let singleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now")]
static let doubleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now"),
ReadReceipt(userID: RoomMemberProxyMock.mockBob.userID, formattedTimestamp: "Before")]
static let tripleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now"),
ReadReceipt(userID: RoomMemberProxyMock.mockBob.userID, formattedTimestamp: "Before"),
ReadReceipt(userID: RoomMemberProxyMock.mockCharlie.userID, formattedTimestamp: "Way before")]
static let quadrupleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now"),
ReadReceipt(userID: RoomMemberProxyMock.mockBob.userID, formattedTimestamp: "Before"),
ReadReceipt(userID: RoomMemberProxyMock.mockCharlie.userID, formattedTimestamp: "Way before"),
ReadReceipt(userID: RoomMemberProxyMock.mockDan.userID, formattedTimestamp: "Way, way before")]
static func mockTimelineItem(with receipts: [ReadReceipt]) -> TextRoomTimelineItem {
TextRoomTimelineItem(id: UUID().uuidString,
timestamp: "Now",
isOutgoing: true,
isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"),
properties: .init(orderedReadReceipts: receipts))
}
static var previews: some View {
VStack {
TimelineReadReceiptsView(timelineItem: mockTimelineItem(with: singleReceipt))
.environmentObject(viewModel.context)
TimelineReadReceiptsView(timelineItem: mockTimelineItem(with: doubleReceipt))
.environmentObject(viewModel.context)
TimelineReadReceiptsView(timelineItem: mockTimelineItem(with: tripleReceipt))
.environmentObject(viewModel.context)
TimelineReadReceiptsView(timelineItem: mockTimelineItem(with: quadrupleReceipt))
.environmentObject(viewModel.context)
}
}
}
@@ -38,7 +38,9 @@ struct AudioRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -37,7 +37,9 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -47,7 +47,9 @@ struct EncryptedRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -38,7 +38,9 @@ struct FileRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -134,7 +134,7 @@ struct FormattedBodyText_Previews: PreviewProvider {
static var previews: some View {
body
body
.timelineStyle(.plain)
.environment(\.timelineStyle, .plain)
}
@ViewBuilder
@@ -58,7 +58,9 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -48,7 +48,9 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -33,7 +33,8 @@ struct StateRoomTimelineView: View {
struct StateRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body
body.timelineStyle(.plain)
body
.environment(\.timelineStyle, .plain)
}
static var body: some View {
@@ -51,7 +51,9 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -37,7 +37,9 @@ struct TextRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -43,7 +43,9 @@ struct UnsupportedRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -70,7 +70,9 @@ struct VideoRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
body.environmentObject(viewModel.context)
body.timelineStyle(.plain).environmentObject(viewModel.context)
body
.environment(\.timelineStyle, .plain)
.environmentObject(viewModel.context)
}
static var body: some View {
@@ -27,10 +27,12 @@ struct DeveloperOptionsScreenViewState: BindableState {
struct DeveloperOptionsScreenViewStateBindings {
var shouldCollapseRoomStateEvents: Bool
var userSuggestionsEnabled: Bool
var readReceiptsEnabled: Bool
}
enum DeveloperOptionsScreenViewAction {
case changedShouldCollapseRoomStateEvents
case changedUserSuggestionsEnabled
case changedReadReceiptsEnabled
case clearCache
}
@@ -26,7 +26,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
init() {
appSettings = ServiceLocator.shared.settings
let bindings = DeveloperOptionsScreenViewStateBindings(shouldCollapseRoomStateEvents: appSettings.shouldCollapseRoomStateEvents,
userSuggestionsEnabled: appSettings.userSuggestionsEnabled)
userSuggestionsEnabled: appSettings.userSuggestionsEnabled,
readReceiptsEnabled: appSettings.readReceiptsEnabled)
let state = DeveloperOptionsScreenViewState(bindings: bindings)
super.init(initialViewState: state)
@@ -42,6 +43,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
appSettings.shouldCollapseRoomStateEvents = state.bindings.shouldCollapseRoomStateEvents
case .changedUserSuggestionsEnabled:
appSettings.userSuggestionsEnabled = state.bindings.userSuggestionsEnabled
case .changedReadReceiptsEnabled:
appSettings.readReceiptsEnabled = state.bindings.readReceiptsEnabled
case .clearCache:
callback?(.clearCache)
}
@@ -36,6 +36,13 @@ struct DeveloperOptionsScreen: View {
.onChange(of: context.userSuggestionsEnabled) { _ in
context.send(viewAction: .changedUserSuggestionsEnabled)
}
Toggle(isOn: $context.readReceiptsEnabled) {
Text("Show read receipts")
}
.onChange(of: context.readReceiptsEnabled) { _ in
context.send(viewAction: .changedReadReceiptsEnabled)
}
}
Section {
@@ -52,7 +52,8 @@ enum RoomTimelineItemFixtures {
isOutgoing: false,
isEditable: false,
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)])),
TextRoomTimelineItem(id: UUID().uuidString,
timestamp: "5 PM",
isOutgoing: true,
@@ -68,7 +69,11 @@ enum RoomTimelineItemFixtures {
properties: RoomTimelineItemProperties(reactions: [
AggregatedReaction(key: "🙏", count: 1, isHighlighted: false),
AggregatedReaction(key: "😁", count: 3, isHighlighted: false)
])),
],
orderedReadReceipts: [ReadReceipt(userID: "alice", formattedTimestamp: nil),
ReadReceipt(userID: "bob", formattedTimestamp: nil),
ReadReceipt(userID: "charlie", formattedTimestamp: nil),
ReadReceipt(userID: "dan", formattedTimestamp: nil)])),
TextRoomTimelineItem(id: UUID().uuidString,
timestamp: "5 PM",
isOutgoing: false,
@@ -85,6 +90,28 @@ enum RoomTimelineItemFixtures {
TextRoomTimelineItem(text: "How are you?",
senderDisplayName: "Alice")]
}
/// A small chunk of events, containing 2 text items.
static var smallChunkWithReadReceipts: [RoomTimelineItemProtocol] {
[TextRoomTimelineItem(text: "Hey there 👋",
senderDisplayName: "Alice")
.withReadReceipts([ReadReceipt(userID: "a1", formattedTimestamp: nil)]),
TextRoomTimelineItem(text: "How are you?",
senderDisplayName: "Alice")
.withReadReceipts([ReadReceipt(userID: "a2", formattedTimestamp: nil),
ReadReceipt(userID: "b2", formattedTimestamp: nil)]),
TextRoomTimelineItem(text: "Fine, Thanks!",
senderDisplayName: "Bob")
.withReadReceipts([ReadReceipt(userID: "a3", formattedTimestamp: nil),
ReadReceipt(userID: "b3", formattedTimestamp: nil),
ReadReceipt(userID: "c3", formattedTimestamp: nil)]),
TextRoomTimelineItem(text: "What about you?",
senderDisplayName: "Bob")
.withReadReceipts([ReadReceipt(userID: "a4", formattedTimestamp: nil),
ReadReceipt(userID: "b4", formattedTimestamp: nil),
ReadReceipt(userID: "c4", formattedTimestamp: nil),
ReadReceipt(userID: "d4", formattedTimestamp: nil)])]
}
/// A chunk of events that contains a single text item.
static var singleMessageChunk: [RoomTimelineItemProtocol] {
@@ -194,3 +221,11 @@ private extension TextRoomTimelineItem {
content: .init(body: text))
}
}
private extension TextRoomTimelineItem {
func withReadReceipts(_ receipts: [ReadReceipt]) -> TextRoomTimelineItem {
var newSelf = self
newSelf.properties.orderedReadReceipts = receipts
return newSelf
}
}
@@ -0,0 +1,26 @@
//
// 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
struct ReadReceipt: Hashable {
let userID: String
let formattedTimestamp: String?
}
extension ReadReceipt: Identifiable {
var id: String { userID }
}
@@ -24,4 +24,6 @@ struct RoomTimelineItemProperties: Hashable {
var reactions: [AggregatedReaction] = []
/// The delivery status for this item. If a sent message is echoed the value is nil.
var deliveryStatus: TimelineItemDeliveryStatus?
/// The read receipts of the item, ordered from newest to oldest
var orderedReadReceipts: [ReadReceipt] = []
}
@@ -132,7 +132,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
aspectRatio: aspectRatio,
blurhash: imageInfo.blurhash,
properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus))
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
@@ -182,7 +183,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyDetails: buildReplyToDetailsFrom(details: messageTimelineItem.inReplyTo()),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus))
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
@@ -198,7 +200,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyDetails: buildReplyToDetailsFrom(details: messageTimelineItem.inReplyTo()),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus))
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
@@ -214,7 +217,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyDetails: buildReplyToDetailsFrom(details: messageTimelineItem.inReplyTo()),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus))
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
@@ -230,7 +234,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyDetails: buildReplyToDetailsFrom(details: messageTimelineItem.inReplyTo()),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus))
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
@@ -246,7 +251,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyDetails: buildReplyToDetailsFrom(details: messageTimelineItem.inReplyTo()),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus))
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
@@ -262,7 +268,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyDetails: buildReplyToDetailsFrom(details: messageTimelineItem.inReplyTo()),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus))
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
@@ -278,7 +285,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
replyDetails: buildReplyToDetailsFrom(details: messageTimelineItem.inReplyTo()),
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus))
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
@@ -287,6 +295,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
return AggregatedReaction(key: reaction.key, count: Int(reaction.count), isHighlighted: isHighlighted)
}
}
private func orderReadReceipts(_ receipts: [String: Receipt]) -> [ReadReceipt] {
receipts
.sorted { firstElement, secondElement in
// If there is no timestamp we order them as last
let firstTimestamp = firstElement.value.dateTimestamp ?? Date(timeIntervalSince1970: 0)
let secondTimestamp = secondElement.value.dateTimestamp ?? Date(timeIntervalSince1970: 0)
return firstTimestamp > secondTimestamp
}
.map { key, receipt in
ReadReceipt(userID: key, formattedTimestamp: receipt.dateTimestamp?.formatted(date: .omitted, time: .shortened))
}
}
// MARK: - Message events content
@@ -139,7 +139,7 @@ enum RoomTimelineViewProvider: Identifiable, Hashable {
extension RoomTimelineViewProvider: View {
var body: some View {
timelineView
.timelineGroupStyle(timelineGroupStyle)
.environment(\.timelineGroupStyle, timelineGroupStyle)
}
@ViewBuilder private var timelineView: some View {
@@ -197,6 +197,18 @@ class MockScreen: Identifiable {
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimelineWithReadReceipts:
ServiceLocator.shared.settings.readReceiptsEnabled = true
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunkWithReadReceipts
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
timelineController: timelineController,
mediaProvider: MockMediaProvider(),
emojiProvider: EmojiProvider())
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimelineIncomingAndSmallPagination:
let navigationStackCoordinator = NavigationStackCoordinator()
@@ -36,6 +36,7 @@ enum UITestsScreenIdentifier: String {
case roomPlainNoAvatar
case roomEncryptedWithAvatar
case roomSmallTimeline
case roomSmallTimelineWithReadReceipts
case roomSmallTimelineIncomingAndSmallPagination
case roomSmallTimelineLargePagination
case roomLayoutTop
+7
View File
@@ -153,6 +153,13 @@ class RoomScreenUITests: XCTestCase {
try await app.assertScreenshot(.roomLayoutBottom, step: 1)
}
func testTimelineReadReceipts() async throws {
let app = Application.launch(.roomSmallTimelineWithReadReceipts)
// The messages should be bottom aligned.
try await app.assertScreenshot(.roomSmallTimelineWithReadReceipts)
}
// MARK: - Helper Methods
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e1a51a634a8e8f9c4c55a8068d7f0cbf6b6658dd31fd8dfc82bb59dd83a9b31c
size 123189
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a64e6084d03b2b61b52528525f39042f95d1a01544d729af236ef81e9359540b
size 188024
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bace67ffd6d2f7df36127fb6499ce7f9af2182d49acb89ab475bc9269e7b7447
size 124167
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:126045309c9d7d8f7ebf5a55076cdc2258ff96c4a91fb018639096445a7e7b62
size 189910