mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
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:
@@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
+24
-14
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -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 {
|
||||
|
||||
+2
@@ -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
|
||||
}
|
||||
|
||||
+4
-1
@@ -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)
|
||||
}
|
||||
|
||||
+7
@@ -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 }
|
||||
}
|
||||
+2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e1a51a634a8e8f9c4c55a8068d7f0cbf6b6658dd31fd8dfc82bb59dd83a9b31c
|
||||
size 123189
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a64e6084d03b2b61b52528525f39042f95d1a01544d729af236ef81e9359540b
|
||||
size 188024
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bace67ffd6d2f7df36127fb6499ce7f9af2182d49acb89ab475bc9269e7b7447
|
||||
size 124167
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:126045309c9d7d8f7ebf5a55076cdc2258ff96c4a91fb018639096445a7e7b62
|
||||
size 189910
|
||||
Reference in New Issue
Block a user