mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
Inlined Timestamp in bubble style for messages that have a bubble (#947)
* web-like solution * not super polished but working implementation * polishing the spacing and sizes * removing unused code * code improvements * adding a test case in the preview for RTL * addressing some PR comments * added some tests and polished the code * better naming * code improvement * RTL fix * Revert "RTL fix" This reverts commit 14e4468a5358769daa57891f4991e9e32da1c985. * better RTL fix * updated UI tests * separated some files * addressed some PR comments * some more tests
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 51;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -408,6 +408,11 @@
|
|||||||
A5D551E5691749066E0E0C44 /* RoomDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */; };
|
A5D551E5691749066E0E0C44 /* RoomDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */; };
|
||||||
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
|
||||||
A6F713461DB62AC06293E7B7 /* FilePreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.swift */; };
|
A6F713461DB62AC06293E7B7 /* FilePreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.swift */; };
|
||||||
|
A733C86A2A1D17590055ECD6 /* TimelineReceiptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A733C8692A1D17590055ECD6 /* TimelineReceiptView.swift */; };
|
||||||
|
A733C86C2A1E149E0055ECD6 /* TextBasedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A733C86B2A1E149E0055ECD6 /* TextBasedRoomTimelineItem.swift */; };
|
||||||
|
A733C86E2A1E1C190055ECD6 /* TextBasedRoomTimelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A733C86D2A1E1C190055ECD6 /* TextBasedRoomTimelineTests.swift */; };
|
||||||
|
A7C152962A1F4E4C0089FF9D /* TextBasedRoomTimelineViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C152952A1F4E4C0089FF9D /* TextBasedRoomTimelineViewProtocol.swift */; };
|
||||||
|
A7C152982A1F4E710089FF9D /* TextBasedRoomTimelineViewMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C152972A1F4E710089FF9D /* TextBasedRoomTimelineViewMock.swift */; };
|
||||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||||
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
|
||||||
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; };
|
||||||
@@ -721,7 +726,7 @@
|
|||||||
1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
|
||||||
13673F95EBA78D40C09CCE35 /* MockUserIndicatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserIndicatorController.swift; sourceTree = "<group>"; };
|
13673F95EBA78D40C09CCE35 /* MockUserIndicatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserIndicatorController.swift; sourceTree = "<group>"; };
|
||||||
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
@@ -832,7 +837,7 @@
|
|||||||
46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = "<group>"; };
|
46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = "<group>"; };
|
||||||
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DesignKit; path = DesignKit; sourceTree = SOURCE_ROOT; };
|
478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DesignKit; sourceTree = SOURCE_ROOT; };
|
||||||
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = "<group>"; };
|
||||||
47E6DD75A81D07CD91997D8C /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
47E6DD75A81D07CD91997D8C /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||||
@@ -973,7 +978,7 @@
|
|||||||
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
|
8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = "<group>"; };
|
||||||
8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = "<group>"; };
|
8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = "<group>"; };
|
||||||
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
|
||||||
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
|
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = "<group>"; };
|
||||||
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
|
8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = "<group>"; };
|
||||||
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
|
8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
8F61A0DD8243B395499C99A2 /* InvitesScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenUITests.swift; sourceTree = "<group>"; };
|
8F61A0DD8243B395499C99A2 /* InvitesScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenUITests.swift; sourceTree = "<group>"; };
|
||||||
@@ -1031,7 +1036,12 @@
|
|||||||
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
||||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
||||||
A6F5CDE754D53A9A403EDBA9 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
A6F5CDE754D53A9A403EDBA9 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
A733C8692A1D17590055ECD6 /* TimelineReceiptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReceiptView.swift; sourceTree = "<group>"; };
|
||||||
|
A733C86B2A1E149E0055ECD6 /* TextBasedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
|
A733C86D2A1E1C190055ECD6 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = "<group>"; };
|
||||||
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
||||||
|
A7C152952A1F4E4C0089FF9D /* TextBasedRoomTimelineViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
A7C152972A1F4E710089FF9D /* TextBasedRoomTimelineViewMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewMock.swift; sourceTree = "<group>"; };
|
||||||
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = "<group>"; };
|
A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -1067,7 +1077,7 @@
|
|||||||
B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemContextMenu.swift; sourceTree = "<group>"; };
|
B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemContextMenu.swift; sourceTree = "<group>"; };
|
||||||
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
|
||||||
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = "<group>"; };
|
||||||
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
|
B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||||
B7DBA101D643B31E813F3AC1 /* AnalyticsSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreen.swift; sourceTree = "<group>"; };
|
B7DBA101D643B31E813F3AC1 /* AnalyticsSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreen.swift; sourceTree = "<group>"; };
|
||||||
@@ -1131,7 +1141,7 @@
|
|||||||
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
||||||
CECF45B5E8E795666B8C5013 /* SettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenModels.swift; sourceTree = "<group>"; };
|
CECF45B5E8E795666B8C5013 /* SettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenModels.swift; sourceTree = "<group>"; };
|
||||||
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
||||||
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
||||||
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
||||||
D06A27D9C70E0DCC1E199163 /* OnboardingBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackgroundView.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>"; };
|
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = "<group>"; };
|
||||||
@@ -1193,7 +1203,7 @@
|
|||||||
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
||||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||||
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
|
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = "<group>"; };
|
||||||
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
|
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||||
@@ -2187,6 +2197,7 @@
|
|||||||
7583EAC171059A86B767209F /* MediaProvider */,
|
7583EAC171059A86B767209F /* MediaProvider */,
|
||||||
7DBC911559934065993A5FF4 /* NotificationManager */,
|
7DBC911559934065993A5FF4 /* NotificationManager */,
|
||||||
1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */,
|
1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */,
|
||||||
|
A733C86D2A1E1C190055ECD6 /* TextBasedRoomTimelineTests.swift */,
|
||||||
);
|
);
|
||||||
path = Sources;
|
path = Sources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2710,6 +2721,9 @@
|
|||||||
F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */,
|
F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */,
|
||||||
A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */,
|
A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */,
|
||||||
1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */,
|
1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */,
|
||||||
|
A733C86B2A1E149E0055ECD6 /* TextBasedRoomTimelineItem.swift */,
|
||||||
|
A7C152952A1F4E4C0089FF9D /* TextBasedRoomTimelineViewProtocol.swift */,
|
||||||
|
A7C152972A1F4E710089FF9D /* TextBasedRoomTimelineViewMock.swift */,
|
||||||
);
|
);
|
||||||
path = Timeline;
|
path = Timeline;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2820,6 +2834,7 @@
|
|||||||
children = (
|
children = (
|
||||||
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */,
|
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */,
|
||||||
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */,
|
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */,
|
||||||
|
A733C8692A1D17590055ECD6 /* TimelineReceiptView.swift */,
|
||||||
);
|
);
|
||||||
path = Supplementary;
|
path = Supplementary;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3516,6 +3531,7 @@
|
|||||||
864C69CF951BF36D25BE0C03 /* DeveloperOptionsScreenViewModelTests.swift in Sources */,
|
864C69CF951BF36D25BE0C03 /* DeveloperOptionsScreenViewModelTests.swift in Sources */,
|
||||||
9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */,
|
9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */,
|
||||||
501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */,
|
501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */,
|
||||||
|
A733C86E2A1E1C190055ECD6 /* TextBasedRoomTimelineTests.swift in Sources */,
|
||||||
25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */,
|
25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */,
|
||||||
71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */,
|
71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */,
|
||||||
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */,
|
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */,
|
||||||
@@ -3636,6 +3652,7 @@
|
|||||||
B98A20A093A4FB785BFCCA53 /* BugReportScreenCoordinator.swift in Sources */,
|
B98A20A093A4FB785BFCCA53 /* BugReportScreenCoordinator.swift in Sources */,
|
||||||
4FFDC274824F7CC0BBDF581E /* BugReportScreenModels.swift in Sources */,
|
4FFDC274824F7CC0BBDF581E /* BugReportScreenModels.swift in Sources */,
|
||||||
8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */,
|
8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */,
|
||||||
|
A733C86C2A1E149E0055ECD6 /* TextBasedRoomTimelineItem.swift in Sources */,
|
||||||
B4A0C69370E6008A971463E7 /* BugReportScreenViewModelProtocol.swift in Sources */,
|
B4A0C69370E6008A971463E7 /* BugReportScreenViewModelProtocol.swift in Sources */,
|
||||||
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */,
|
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */,
|
||||||
172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */,
|
172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */,
|
||||||
@@ -3872,6 +3889,7 @@
|
|||||||
77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */,
|
77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */,
|
||||||
CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */,
|
CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */,
|
||||||
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */,
|
B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */,
|
||||||
|
A7C152962A1F4E4C0089FF9D /* TextBasedRoomTimelineViewProtocol.swift in Sources */,
|
||||||
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */,
|
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */,
|
||||||
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */,
|
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */,
|
||||||
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */,
|
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */,
|
||||||
@@ -3936,6 +3954,8 @@
|
|||||||
157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */,
|
157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */,
|
||||||
01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */,
|
01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */,
|
||||||
FBCCF1EA25A071324FCD8544 /* TimelineItemDebugView.swift in Sources */,
|
FBCCF1EA25A071324FCD8544 /* TimelineItemDebugView.swift in Sources */,
|
||||||
|
A733C86A2A1D17590055ECD6 /* TimelineReceiptView.swift in Sources */,
|
||||||
|
A7C152982A1F4E710089FF9D /* TextBasedRoomTimelineViewMock.swift in Sources */,
|
||||||
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */,
|
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */,
|
||||||
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
|
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
|
||||||
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */,
|
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */,
|
||||||
|
|||||||
@@ -62,3 +62,22 @@ extension String {
|
|||||||
return mutableString.trimmingCharacters(in: .whitespaces)
|
return mutableString.trimmingCharacters(in: .whitespaces)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
static func generateBreakableWhitespaceEnd(whitespaceCount: Int, isRTL: Bool) -> String {
|
||||||
|
guard whitespaceCount > 0 else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var whiteSpaces = ""
|
||||||
|
if isRTL {
|
||||||
|
whiteSpaces = "\u{202e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixed size whitespace of size 1/3 em per character
|
||||||
|
whiteSpaces += String(repeating: "\u{2004}", count: whitespaceCount)
|
||||||
|
|
||||||
|
// braille whitespace, which is non breakable but makes previous whitespaces breakable
|
||||||
|
return whiteSpaces + "\u{2800}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+50
-23
@@ -17,17 +17,20 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TimelineItemBubbledStylerView<Content: View, DeliveryStatus: View>: View {
|
struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
|
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
|
||||||
|
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
@ViewBuilder let deliveryStatus: () -> DeliveryStatus
|
|
||||||
|
|
||||||
@ScaledMetric private var senderNameVerticalPadding = 3
|
@ScaledMetric private var senderNameVerticalPadding = 3
|
||||||
private let cornerRadius: CGFloat = 12
|
private let cornerRadius: CGFloat = 12
|
||||||
|
|
||||||
|
private var isTextItem: Bool {
|
||||||
|
timelineItem is TextBasedRoomTimelineItem
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .trailingFirstTextBaseline) {
|
ZStack(alignment: .trailingFirstTextBaseline) {
|
||||||
VStack(alignment: alignment, spacing: -12) {
|
VStack(alignment: alignment, spacing: -12) {
|
||||||
@@ -83,7 +86,8 @@ struct TimelineItemBubbledStylerView<Content: View, DeliveryStatus: View>: View
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deliveryStatus()
|
TimelineReceiptView(timelineItem: timelineItem)
|
||||||
|
.environmentObject(context)
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
.padding(.bottom, 3)
|
.padding(.bottom, 3)
|
||||||
}
|
}
|
||||||
@@ -108,28 +112,48 @@ struct TimelineItemBubbledStylerView<Content: View, DeliveryStatus: View>: View
|
|||||||
cornerRadius: cornerRadius,
|
cornerRadius: cornerRadius,
|
||||||
corners: roundedCorners)
|
corners: roundedCorners)
|
||||||
} else {
|
} else {
|
||||||
VStack(alignment: .trailing, spacing: 4) {
|
contentWithTimestamp
|
||||||
contentWithReply
|
.bubbleStyle(inset: true,
|
||||||
|
color: timelineItem.isOutgoing ? .element.bubblesYou : .element.bubblesNotYou,
|
||||||
if timelineItem.properties.isEdited {
|
cornerRadius: cornerRadius,
|
||||||
Text(L10n.commonEditedSuffix)
|
corners: roundedCorners)
|
||||||
.font(.compound.bodyXS)
|
|
||||||
.foregroundColor(.element.tertiaryContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
if timelineItem.properties.deliveryStatus == .sendingFailed {
|
|
||||||
Image(systemName: "exclamationmark.circle.fill")
|
|
||||||
.resizable()
|
|
||||||
.foregroundColor(.element.alert)
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bubbleStyle(inset: true,
|
|
||||||
color: timelineItem.isOutgoing ? .element.bubblesYou : .element.bubblesNotYou,
|
|
||||||
cornerRadius: cornerRadius,
|
|
||||||
corners: roundedCorners)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
var contentWithTimestamp: some View {
|
||||||
|
if isTextItem {
|
||||||
|
ZStack(alignment: .topLeading) {
|
||||||
|
contentWithReply
|
||||||
|
.layoutPriority(1)
|
||||||
|
localizedSendInfo
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HStack(alignment: .bottom, spacing: 4) {
|
||||||
|
contentWithReply
|
||||||
|
localizedSendInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
var localizedSendInfo: some View {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
if let timelineItem = timelineItem as? TextBasedRoomTimelineItem {
|
||||||
|
Text(timelineItem.localizedSendInfo)
|
||||||
|
} else {
|
||||||
|
Text(timelineItem.timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timelineItem.properties.deliveryStatus == .sendingFailed {
|
||||||
|
Image(systemName: "exclamationmark.circle.fill")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.compound.bodyXS)
|
||||||
|
.foregroundColor(timelineItem.properties.deliveryStatus == .sendingFailed ? .element.alert : .element.secondaryContent)
|
||||||
|
.padding(.bottom, -4)
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var contentWithReply: some View {
|
var contentWithReply: some View {
|
||||||
@@ -213,6 +237,9 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
mockTimeline
|
mockTimeline
|
||||||
.previewDisplayName("Mock Timeline")
|
.previewDisplayName("Mock Timeline")
|
||||||
|
mockTimeline
|
||||||
|
.environment(\.layoutDirection, .rightToLeft)
|
||||||
|
.previewDisplayName("Mock Timeline RTL")
|
||||||
replies
|
replies
|
||||||
.previewDisplayName("Replies")
|
.previewDisplayName("Replies")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,12 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TimelineItemPlainStylerView<Content: View, DeliveryStatus: View>: View {
|
struct TimelineItemPlainStylerView<Content: View>: View {
|
||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
|
@Environment(\.timelineGroupStyle) private var timelineGroupStyle
|
||||||
|
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
@ViewBuilder let deliveryStatus: () -> DeliveryStatus
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .trailing) {
|
VStack(alignment: .trailing) {
|
||||||
@@ -39,7 +38,8 @@ struct TimelineItemPlainStylerView<Content: View, DeliveryStatus: View>: View {
|
|||||||
supplementaryViews
|
supplementaryViews
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deliveryStatus()
|
TimelineReceiptView(timelineItem: timelineItem)
|
||||||
|
.environmentObject(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,6 @@ import SwiftUI
|
|||||||
|
|
||||||
struct TimelineStyler<Content: View>: View {
|
struct TimelineStyler<Content: View>: View {
|
||||||
@Environment(\.timelineStyle) private var style
|
@Environment(\.timelineStyle) private var style
|
||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
|
||||||
|
|
||||||
private var isLastOutgoingMessage: Bool {
|
|
||||||
context.viewState.items.last(where: { !$0.isUnsent })?.id == timelineItem.id &&
|
|
||||||
timelineItem.isOutgoing
|
|
||||||
}
|
|
||||||
|
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
@@ -34,36 +28,9 @@ struct TimelineStyler<Content: View>: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
switch style {
|
switch style {
|
||||||
case .plain:
|
case .plain:
|
||||||
TimelineItemPlainStylerView(timelineItem: timelineItem, content: content) {
|
TimelineItemPlainStylerView(timelineItem: timelineItem, content: content)
|
||||||
deliveryStatusView
|
|
||||||
}
|
|
||||||
case .bubbles:
|
case .bubbles:
|
||||||
TimelineItemBubbledStylerView(timelineItem: timelineItem, content: content) {
|
TimelineItemBubbledStylerView(timelineItem: timelineItem, content: content)
|
||||||
deliveryStatusView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var deliveryStatusView: some View {
|
|
||||||
switch timelineItem.properties.deliveryStatus {
|
|
||||||
case .sending:
|
|
||||||
TimelineDeliveryStatusView(deliveryStatus: .sending)
|
|
||||||
case .sent:
|
|
||||||
TimelineDeliveryStatusView(deliveryStatus: .sent)
|
|
||||||
case .none:
|
|
||||||
if isLastOutgoingMessage {
|
|
||||||
// We always display the sent icon for the latest echoed outgoing message
|
|
||||||
TimelineDeliveryStatusView(deliveryStatus: .sent)
|
|
||||||
}
|
|
||||||
case .sendingFailed:
|
|
||||||
if style == .plain {
|
|
||||||
Image(systemName: "exclamationmark.circle.fill")
|
|
||||||
.resizable()
|
|
||||||
.foregroundColor(.element.alert)
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
}
|
|
||||||
// The bubbles handle the failure internally
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +38,7 @@ struct TimelineStyler<Content: View>: View {
|
|||||||
struct TimelineItemStyler_Previews: PreviewProvider {
|
struct TimelineItemStyler_Previews: PreviewProvider {
|
||||||
static let viewModel = RoomScreenViewModel.mock
|
static let viewModel = RoomScreenViewModel.mock
|
||||||
|
|
||||||
static let base = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: ""))
|
static let base = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
|
|
||||||
static let sent: TextRoomTimelineItem = {
|
static let sent: TextRoomTimelineItem = {
|
||||||
var result = base
|
var result = base
|
||||||
@@ -93,27 +60,17 @@ struct TimelineItemStyler_Previews: PreviewProvider {
|
|||||||
|
|
||||||
static let last: TextRoomTimelineItem = {
|
static let last: TextRoomTimelineItem = {
|
||||||
let id = viewModel.state.items.last?.id ?? UUID().uuidString
|
let id = viewModel.state.items.last?.id ?? UUID().uuidString
|
||||||
let result = TextRoomTimelineItem(id: id, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: ""))
|
let result = TextRoomTimelineItem(id: id, timestamp: "Now", isOutgoing: true, isEditable: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static var testView: some View {
|
static var testView: some View {
|
||||||
VStack {
|
VStack {
|
||||||
TimelineStyler(timelineItem: sent) {
|
TextRoomTimelineView(timelineItem: sent)
|
||||||
Text("Sent")
|
TextRoomTimelineView(timelineItem: sending)
|
||||||
}
|
TextRoomTimelineView(timelineItem: base)
|
||||||
TimelineStyler(timelineItem: sending) {
|
TextRoomTimelineView(timelineItem: last)
|
||||||
Text("Sending")
|
TextRoomTimelineView(timelineItem: failed)
|
||||||
}
|
|
||||||
TimelineStyler(timelineItem: base) {
|
|
||||||
Text("Normal")
|
|
||||||
}
|
|
||||||
TimelineStyler(timelineItem: last) {
|
|
||||||
Text("Last")
|
|
||||||
}
|
|
||||||
TimelineStyler(timelineItem: failed) {
|
|
||||||
Text("Failed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// This is also going to be used for read receipts in the future
|
||||||
|
struct TimelineReceiptView: View {
|
||||||
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
|
@Environment(\.timelineStyle) private var style
|
||||||
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
|
|
||||||
|
@State private var shouldShowDeliveryStatus = true
|
||||||
|
|
||||||
|
private var isLastOutgoingMessage: Bool {
|
||||||
|
context.viewState.items.last(where: { !$0.isUnsent })?.id == timelineItem.id &&
|
||||||
|
timelineItem.isOutgoing
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isLast: Bool {
|
||||||
|
context.viewState.items.last?.id == timelineItem.id
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if shouldShowDeliveryStatus {
|
||||||
|
deliveryStatus
|
||||||
|
.onChange(of: timelineItem.properties.deliveryStatus) { newValue in
|
||||||
|
if newValue == .sent, !isLast {
|
||||||
|
Task {
|
||||||
|
try? await Task.sleep(for: .milliseconds(1500))
|
||||||
|
withAnimation {
|
||||||
|
shouldShowDeliveryStatus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
var deliveryStatus: some View {
|
||||||
|
switch timelineItem.properties.deliveryStatus {
|
||||||
|
case .sending:
|
||||||
|
TimelineDeliveryStatusView(deliveryStatus: .sending)
|
||||||
|
case .sent:
|
||||||
|
TimelineDeliveryStatusView(deliveryStatus: .sent)
|
||||||
|
case .none:
|
||||||
|
if isLastOutgoingMessage {
|
||||||
|
// We always display the sent icon for the latest echoed outgoing message
|
||||||
|
TimelineDeliveryStatusView(deliveryStatus: .sent)
|
||||||
|
}
|
||||||
|
case .sendingFailed:
|
||||||
|
if style == .plain {
|
||||||
|
Image(systemName: "exclamationmark.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.foregroundColor(.element.alert)
|
||||||
|
.frame(width: 16, height: 16)
|
||||||
|
}
|
||||||
|
// The bubbles handle the failure internally
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,15 +17,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct EmoteRoomTimelineView: View {
|
struct EmoteRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||||
|
@Environment(\.timelineStyle) var timelineStyle
|
||||||
let timelineItem: EmoteRoomTimelineItem
|
let timelineItem: EmoteRoomTimelineItem
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
if let attributedString = timelineItem.content.formattedBody {
|
if let attributedString = timelineItem.content.formattedBody {
|
||||||
FormattedBodyText(attributedString: attributedString)
|
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces)
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(text: timelineItem.content.body)
|
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: additionalWhitespaces)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,25 @@ import SwiftUI
|
|||||||
|
|
||||||
struct FormattedBodyText: View {
|
struct FormattedBodyText: View {
|
||||||
@Environment(\.timelineStyle) private var timelineStyle
|
@Environment(\.timelineStyle) private var timelineStyle
|
||||||
|
@Environment(\.layoutDirection) private var layoutDirection
|
||||||
|
|
||||||
|
private let attributedString: AttributedString
|
||||||
|
private let additionalWhitespacesCount: Int
|
||||||
|
|
||||||
|
private var attributedComponents: [AttributedStringBuilderComponent] {
|
||||||
|
var attributedString = attributedString
|
||||||
|
attributedString.append(AttributedString(stringLiteral: additionalWhitespacesSuffix))
|
||||||
|
return attributedString.formattedComponents
|
||||||
|
}
|
||||||
|
|
||||||
private let attributedComponents: [AttributedStringBuilderComponent]
|
init(attributedString: AttributedString, additionalWhitespacesCount: Int = 0) {
|
||||||
|
self.attributedString = attributedString
|
||||||
init(attributedString: AttributedString) {
|
self.additionalWhitespacesCount = additionalWhitespacesCount
|
||||||
attributedComponents = attributedString.formattedComponents
|
}
|
||||||
|
|
||||||
|
// These is needed to create the slightly off inlined timestamp effect
|
||||||
|
private var additionalWhitespacesSuffix: String {
|
||||||
|
.generateBreakableWhitespaceEnd(whitespaceCount: additionalWhitespacesCount, isRTL: layoutDirection == .rightToLeft)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -109,8 +123,8 @@ struct FormattedBodyText: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension FormattedBodyText {
|
extension FormattedBodyText {
|
||||||
init(text: String) {
|
init(text: String, additionalWhitespacesCount: Int = 0) {
|
||||||
self.init(attributedString: AttributedString(text))
|
self.init(attributedString: AttributedString(text), additionalWhitespacesCount: additionalWhitespacesCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct NoticeRoomTimelineView: View {
|
struct NoticeRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||||
let timelineItem: NoticeRoomTimelineItem
|
let timelineItem: NoticeRoomTimelineItem
|
||||||
|
@Environment(\.timelineStyle) var timelineStyle
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
@@ -32,9 +33,9 @@ struct NoticeRoomTimelineView: View {
|
|||||||
.foregroundColor(.element.secondaryContent)
|
.foregroundColor(.element.secondaryContent)
|
||||||
|
|
||||||
if let attributedString = timelineItem.content.formattedBody {
|
if let attributedString = timelineItem.content.formattedBody {
|
||||||
FormattedBodyText(attributedString: attributedString)
|
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces)
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(text: timelineItem.content.body)
|
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: additionalWhitespaces)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.leading, 4) // Trailing padding is provided by FormattedBodyText
|
.padding(.leading, 4) // Trailing padding is provided by FormattedBodyText
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
protocol TextBasedRoomTimelineItem: EventBasedMessageTimelineItemProtocol { }
|
||||||
|
|
||||||
|
extension TextBasedRoomTimelineItem {
|
||||||
|
/// contains the timestamp and an optional edited localised prefix
|
||||||
|
/// example: (edited) 12:17 PM
|
||||||
|
var localizedSendInfo: String {
|
||||||
|
var start = ""
|
||||||
|
if properties.isEdited {
|
||||||
|
start = "\(L10n.commonEditedSuffix) "
|
||||||
|
}
|
||||||
|
return start + timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// generated with auto mockable and customised to support the generic
|
||||||
|
class TextBasedRoomTimelineViewMock<TimelineItemType: TextBasedRoomTimelineItem>: TextBasedRoomTimelineViewProtocol {
|
||||||
|
var timelineItem: TimelineItemType {
|
||||||
|
get { underlyingTimelineItem }
|
||||||
|
set(value) { underlyingTimelineItem = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var underlyingTimelineItem: TimelineItemType!
|
||||||
|
var timelineStyle: TimelineStyle {
|
||||||
|
get { underlyingTimelineStyle }
|
||||||
|
set(value) { underlyingTimelineStyle = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var underlyingTimelineStyle: TimelineStyle!
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
protocol TextBasedRoomTimelineViewProtocol {
|
||||||
|
associatedtype TimelineItemType: TextBasedRoomTimelineItem
|
||||||
|
|
||||||
|
var timelineItem: TimelineItemType { get }
|
||||||
|
var timelineStyle: TimelineStyle { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TextBasedRoomTimelineViewProtocol {
|
||||||
|
var additionalWhitespaces: Int {
|
||||||
|
guard timelineStyle == .bubbles else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var whiteSpaces = 1
|
||||||
|
timelineItem.localizedSendInfo.forEach { _ in
|
||||||
|
whiteSpaces += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// To account for the extra spacing created by the alert icon
|
||||||
|
if timelineItem.properties.deliveryStatus == .sendingFailed {
|
||||||
|
whiteSpaces += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
return whiteSpaces
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,15 +17,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TextRoomTimelineView: View {
|
struct TextRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||||
let timelineItem: TextRoomTimelineItem
|
let timelineItem: TextRoomTimelineItem
|
||||||
|
@Environment(\.timelineStyle) var timelineStyle
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
if let attributedString = timelineItem.content.formattedBody {
|
if let attributedString = timelineItem.content.formattedBody {
|
||||||
FormattedBodyText(attributedString: attributedString)
|
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces)
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(text: timelineItem.content.body)
|
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: additionalWhitespaces)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct EmoteRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
||||||
let id: String
|
let id: String
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct NoticeRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
||||||
let id: String
|
let id: String
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
let isOutgoing: Bool
|
let isOutgoing: Bool
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct TextRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
||||||
let id: String
|
let id: String
|
||||||
|
|
||||||
let timestamp: String
|
let timestamp: String
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class RoomScreenUITests: XCTestCase {
|
|||||||
defer { try? client.stop() }
|
defer { try? client.stop() }
|
||||||
|
|
||||||
// Given a timeline that is scrolled to the top.
|
// Given a timeline that is scrolled to the top.
|
||||||
while !app.staticTexts["Bacon ipsum dolor amet commodo incididunt ribeye dolore cupidatat short ribs."].isHittable {
|
for _ in 0...5 {
|
||||||
app.tables.element.swipeDown()
|
app.tables.element.swipeDown()
|
||||||
}
|
}
|
||||||
let cropped = UIEdgeInsets(top: 150, left: 0, bottom: 0, right: 0) // Ignore the navigation bar and pagination indicator as these change.
|
let cropped = UIEdgeInsets(top: 150, left: 0, bottom: 0, right: 0) // Ignore the navigation bar and pagination indicator as these change.
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0e61e6dccf47027af9b1d9d40b28a38ab9b75b4455cfc4dd93835748caf095b7
|
oid sha256:a4983811f9c93ae9d91957ad62b91456beed84048abbc78339ccd159edb9b6e9
|
||||||
size 226144
|
size 234145
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:72f234c5fe48fb4a1faa736c34d35a9bb693c3d3fa2b729a09df0602ca072ef5
|
oid sha256:a1ddd83f9f845b6d6281f8e291674957f9d3b2d6d5dfad66b3ead58cf3faa147
|
||||||
size 266282
|
size 278646
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:23c7126fd1fa69e482220b3a1e1fc3608b72e8a7dacb1f2abf5b8d5b95befb1b
|
oid sha256:b24385ffd83f5124614ec088dbcc96ac80b717eeba49b0e87624b308a6a00875
|
||||||
size 226023
|
size 234046
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1071fdafcad66664288bc18505f733829f045c07faecaeb36d62be896262a3a8
|
oid sha256:d16782fe705cc6c2d5dcf0c5e60ee72898c599c8226e6fc3abdbe0b2f6ede5f7
|
||||||
size 96975
|
size 100181
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8f8de7ead20eef42d189b3582169783eaa037478ca43c9f1004f216abfc2b3e9
|
oid sha256:863a56b98b4781de1da9122817c84df6791afd25defc3b9709737c9417e72481
|
||||||
size 116418
|
size 122963
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a3204698079695ad6197fbdba23e1e45322f3981a202019bbedf44fda401dc4d
|
oid sha256:17578cec39bfddd1b1ff1963b74ce1e9863d073f133de441e0997a21c5bfdf37
|
||||||
size 284896
|
size 298067
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:891bd87182b1ce91da99b4748b4c951eeea0f8cb81355628a3f6cf75d97c1e6a
|
oid sha256:e44ebb4db0febc50dd1ae363c106d149e4a8e6e0386eb7c6e2b6b442a3784535
|
||||||
size 350090
|
size 358633
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:71b1a302c3013f61056be9c3aad8d429aad25f402a38b7ad47fca32999b6e5d3
|
oid sha256:81b08daf45dccae39110df315f14d0aeb5027b805af18bee10ba8fa561fb980a
|
||||||
size 248611
|
size 246221
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2d5cf517ea15a9a5fdfcacc858b7b3173cdc1a9bda0afa975b4e271f4afdc0c9
|
oid sha256:0160aea39bc6016c5e423d2d846f247d1f1e9d0024a1f5c833a98be7e77ea207
|
||||||
size 349837
|
size 358357
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7109c790683af45b2f29fd0a6aa8496ac09325e48a15a27d6abde5212139acad
|
oid sha256:9fc157e760cd2faf5936011d69f53744b7d037c9ee8f8434d071ff49e626e9da
|
||||||
size 150619
|
size 155517
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b7c6bac06cdaa0b3f126eecbfe1cf232ac34707e065f13fbaf68708c58cf2293
|
oid sha256:c849dbf18fda70046a9cc5e8a117078a69c7b5618222710c92f437b288217d74
|
||||||
size 181693
|
size 191841
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1961e6e76df75e7d3fcb5e385fa66f5d8895e0dc3a48d5b12d42ef37253488ec
|
oid sha256:77e6a585c3e6057cbb2c92f7b525b1464c13570a67b809ba6c02a4f09b7e0a60
|
||||||
size 302074
|
size 318416
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6ee1d5f6d28f2edc5fa5446e8e9df6268df8e543dd83230f05001f6a502b54da
|
oid sha256:845e339376c379882e2a4811a4349cb89377abd348282a76b29640ec3ed5e551
|
||||||
size 227470
|
size 235185
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c80d586a3bac65665769abdaea9bd94819eb0e5796aa5b84d668638124e95b73
|
oid sha256:5460a8e7171dccc726fc7a5da386d0408f86998ea4d51b06b05c90f077b8bc21
|
||||||
size 266691
|
size 279028
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6e24bd90d4861049fd1473e81e237f35253063544fa5205ead132566fa3f4c76
|
oid sha256:f7b7650aab00b11152a22a63721b2005364d1e915c44cd802a29ced4f5f586ab
|
||||||
size 227347
|
size 235091
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7590bf3ce258bd1ed83674c7aa8fe029cc6b65cc789e60db6c5aac75f88de1c5
|
oid sha256:d413f50e7778b19fdb8baac0e11b6f78c76af3b58436e405a6f60a9b42a50940
|
||||||
size 97468
|
size 100669
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:aff34dd5e386da4c9491eb19bcb6e2ecd40301640be3768f04f40b2f447dcda2
|
oid sha256:283db285833ed3fd65ae6adf61111ffca3f3a9d5fc3fb05d240f81fac7ea8b17
|
||||||
size 116895
|
size 123454
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:55e1b3c880fc7589c9d955605e16d3df33d842d37fff91efd31acc787d817ee6
|
oid sha256:3b7fb769b14c81ff5e9d9be36083ffa48623c90ea4d333105c071bf326d0c859
|
||||||
size 285371
|
size 298553
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:270122db18781f10a4357610bec368381c8977f36cd81ebabd16ab8abefae822
|
oid sha256:a2ab1da0274529f2bc26af0b7d9d94ad399a5c46e6f3a496df3885d1cd650729
|
||||||
size 350678
|
size 332000
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8fb535d6c0766d034d32cf2488acbee08053d620dec8f33570506073eca44af7
|
oid sha256:8f2115478483a5e24fa09118e00efc8eedb79db01782b7a54ef0e70c5baf27b3
|
||||||
size 249202
|
size 239702
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d11402df691d0fce38b74683a8e3e21915ad592ce8ef9a72c6a0947e8a5af30d
|
oid sha256:b4764bac791d16b80ab2a2303e97bad7eb5971b45439319b8f633752718279cd
|
||||||
size 350425
|
size 331646
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8feb34445e1fcb0d2fe2061a225428f5658436a8821a722b31fefb6e0063f7c8
|
oid sha256:a1ef9a0b33c1c8c9fbae6fc66f0d9bbdbda390574539c6c2bfc94c1c8e3ac81a
|
||||||
size 151125
|
size 156266
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:26051b89f610c6d5810559df462951cbe4f82ba0dba02568d27052ec7ce895f1
|
oid sha256:8fc2fcf3937665fe3a581c9378d023ca57f4d5e630eb51496d7f21a1e4fe9a8f
|
||||||
size 182242
|
size 192441
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7484ae40068b388b98e271f962529e44c67b514c2e8a7bac934bfbbfeb4b9903
|
oid sha256:643fdf24fa8f7553f120da069ddbfe13d5b2e2405ad2b0041ad765b0efd99797
|
||||||
size 302656
|
size 305866
|
||||||
|
|||||||
@@ -58,4 +58,26 @@ class StringTests: XCTestCase {
|
|||||||
func testStripsTheHeartbreakHotel() {
|
func testStripsTheHeartbreakHotel() {
|
||||||
XCTAssertEqual("Heartbreak Hotel 🏩".asciified(), "Heartbreak Hotel")
|
XCTAssertEqual("Heartbreak Hotel 🏩".asciified(), "Heartbreak Hotel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGenerateBreakableWhitespaceEnd() {
|
||||||
|
var count = 5
|
||||||
|
var result = String(repeating: "\u{2004}", count: count) + "\u{2800}"
|
||||||
|
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: false), result)
|
||||||
|
|
||||||
|
count = 3
|
||||||
|
result = String(repeating: "\u{2004}", count: count) + "\u{2800}"
|
||||||
|
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: false), result)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
result = ""
|
||||||
|
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: false), result)
|
||||||
|
|
||||||
|
count = 4
|
||||||
|
result = "\u{202e}" + String(repeating: "\u{2004}", count: count) + "\u{2800}"
|
||||||
|
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: true), result)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
result = ""
|
||||||
|
XCTAssertEqual(String.generateBreakableWhitespaceEnd(whitespaceCount: count, isRTL: true), result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
@testable import ElementX
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class TextBasedRoomTimelineTests: XCTestCase {
|
||||||
|
func testTextRoomTimelineItemWhitespaceEnd() {
|
||||||
|
let timestamp = "Now"
|
||||||
|
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
|
view.underlyingTimelineItem = timelineItem
|
||||||
|
view.timelineStyle = .bubbles
|
||||||
|
|
||||||
|
XCTAssertEqual(view.additionalWhitespaces, timestamp.count + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTextRoomTimelineItemWhitespaceEndLonger() {
|
||||||
|
let timestamp = "10:00 AM"
|
||||||
|
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
|
view.underlyingTimelineItem = timelineItem
|
||||||
|
view.timelineStyle = .bubbles
|
||||||
|
|
||||||
|
XCTAssertEqual(view.additionalWhitespaces, timestamp.count + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTextRoomTimelineItemWhitespaceEndPlain() {
|
||||||
|
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: "Now", isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
|
view.underlyingTimelineItem = timelineItem
|
||||||
|
view.timelineStyle = .plain
|
||||||
|
|
||||||
|
XCTAssertEqual(view.additionalWhitespaces, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTextRoomTimelineItemWhitespaceEndWithEdit() {
|
||||||
|
let timestamp = "Now"
|
||||||
|
var timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
|
timelineItem.properties.isEdited = true
|
||||||
|
let editedCount = L10n.commonEditedSuffix.count
|
||||||
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
|
view.underlyingTimelineItem = timelineItem
|
||||||
|
view.timelineStyle = .bubbles
|
||||||
|
|
||||||
|
XCTAssertEqual(view.additionalWhitespaces, timestamp.count + editedCount + 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTextRoomTimelineItemWhitespaceEndWithEditAndAlert() {
|
||||||
|
let timestamp = "Now"
|
||||||
|
var timelineItem = TextRoomTimelineItem(id: UUID().uuidString, timestamp: timestamp, isOutgoing: true, isEditable: true, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
|
||||||
|
timelineItem.properties.isEdited = true
|
||||||
|
timelineItem.properties.deliveryStatus = .sendingFailed
|
||||||
|
let editedCount = L10n.commonEditedSuffix.count
|
||||||
|
let view = TextBasedRoomTimelineViewMock<TextRoomTimelineItem>()
|
||||||
|
view.underlyingTimelineItem = timelineItem
|
||||||
|
view.timelineStyle = .bubbles
|
||||||
|
|
||||||
|
XCTAssertEqual(view.additionalWhitespaces, timestamp.count + editedCount + 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Timestamp for messages incorporated in a bubble.
|
||||||
Reference in New Issue
Block a user