From db2b2b7e34d7811b04fdf742298a901ebd303c7a Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Thu, 25 May 2023 12:13:45 +0200 Subject: [PATCH] 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 --- ElementX.xcodeproj/project.pbxproj | 34 +++++++-- .../Sources/Other/Extensions/String.swift | 19 +++++ .../Style/TimelineItemBubbledStylerView.swift | 73 ++++++++++++------ .../Style/TimelineItemPlainStylerView.swift | 6 +- .../View/Style/TimelineStyler.swift | 61 +++------------ .../Supplementary/TimelineReceiptView.swift | 74 +++++++++++++++++++ .../View/Timeline/EmoteRoomTimelineView.swift | 7 +- .../View/Timeline/FormattedBodyText.swift | 26 +++++-- .../Timeline/NoticeRoomTimelineView.swift | 7 +- .../Timeline/TextBasedRoomTimelineItem.swift | 31 ++++++++ .../TextBasedRoomTimelineViewMock.swift | 33 +++++++++ .../TextBasedRoomTimelineViewProtocol.swift | 41 ++++++++++ .../View/Timeline/TextRoomTimelineView.swift | 7 +- .../Messages/EmoteRoomTimelineItem.swift | 2 +- .../Messages/NoticeRoomTimelineItem.swift | 2 +- .../Items/Messages/TextRoomTimelineItem.swift | 2 +- UITests/Sources/RoomScreenUITests.swift | 2 +- ...9th-generation.roomEncryptedWithAvatar.png | 4 +- ...n-GB-iPad-9th-generation.roomLayoutTop.png | 4 +- ...-iPad-9th-generation.roomPlainNoAvatar.png | 4 +- ...-iPad-9th-generation.roomSmallTimeline.png | 4 +- ...mallTimelineIncomingAndSmallPagination.png | 4 +- ...ation.roomSmallTimelineLargePagination.png | 4 +- ...n-GB-iPhone-14.roomEncryptedWithAvatar.png | 4 +- .../en-GB-iPhone-14.roomLayoutTop.png | 4 +- .../en-GB-iPhone-14.roomPlainNoAvatar.png | 4 +- .../en-GB-iPhone-14.roomSmallTimeline.png | 4 +- ...mallTimelineIncomingAndSmallPagination.png | 4 +- ...ne-14.roomSmallTimelineLargePagination.png | 4 +- ...9th-generation.roomEncryptedWithAvatar.png | 4 +- ...eudo-iPad-9th-generation.roomLayoutTop.png | 4 +- ...-iPad-9th-generation.roomPlainNoAvatar.png | 4 +- ...-iPad-9th-generation.roomSmallTimeline.png | 4 +- ...mallTimelineIncomingAndSmallPagination.png | 4 +- ...ation.roomSmallTimelineLargePagination.png | 4 +- ...eudo-iPhone-14.roomEncryptedWithAvatar.png | 4 +- .../pseudo-iPhone-14.roomLayoutTop.png | 4 +- .../pseudo-iPhone-14.roomPlainNoAvatar.png | 4 +- .../pseudo-iPhone-14.roomSmallTimeline.png | 4 +- ...mallTimelineIncomingAndSmallPagination.png | 4 +- ...ne-14.roomSmallTimelineLargePagination.png | 4 +- UnitTests/Sources/StringTests.swift | 22 ++++++ .../Sources/TextBasedRoomTimelineTests.swift | 74 +++++++++++++++++++ changelog.d/948.feature | 1 + 44 files changed, 468 insertions(+), 152 deletions(-) create mode 100644 ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReceiptView.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineItem.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineViewMock.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineViewProtocol.swift create mode 100644 UnitTests/Sources/TextBasedRoomTimelineTests.swift create mode 100644 changelog.d/948.feature diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 37995c256..1e3fa1b0e 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -408,6 +408,11 @@ A5D551E5691749066E0E0C44 /* RoomDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837B440C4705E4B899BCB899 /* RoomDetailsScreenViewModel.swift */; }; A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.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 */; }; A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.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 = ""; }; 127A57D053CE8C87B5EFB089 /* Consumable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = ""; }; 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13673F95EBA78D40C09CCE35 /* MockUserIndicatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserIndicatorController.swift; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -832,7 +837,7 @@ 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = ""; }; 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = ""; }; - 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 = ""; }; 47E6DD75A81D07CD91997D8C /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; @@ -973,7 +978,7 @@ 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = ""; }; 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineProvider.swift; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = ""; }; 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = ""; }; 8F61A0DD8243B395499C99A2 /* InvitesScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenUITests.swift; sourceTree = ""; }; @@ -1031,7 +1036,12 @@ A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = ""; }; A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = ""; }; A6F5CDE754D53A9A403EDBA9 /* DeveloperOptionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModelProtocol.swift; sourceTree = ""; }; + A733C8692A1D17590055ECD6 /* TimelineReceiptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReceiptView.swift; sourceTree = ""; }; + A733C86B2A1E149E0055ECD6 /* TextBasedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineItem.swift; sourceTree = ""; }; + A733C86D2A1E1C190055ECD6 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = ""; }; A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = ""; }; + A7C152952A1F4E4C0089FF9D /* TextBasedRoomTimelineViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewProtocol.swift; sourceTree = ""; }; + A7C152972A1F4E710089FF9D /* TextBasedRoomTimelineViewMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewMock.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = ""; }; A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = ""; }; @@ -1067,7 +1077,7 @@ B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemContextMenu.swift; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; B7DBA101D643B31E813F3AC1 /* AnalyticsSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreen.swift; sourceTree = ""; }; @@ -1131,7 +1141,7 @@ CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CECF45B5E8E795666B8C5013 /* SettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenModels.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; D06A27D9C70E0DCC1E199163 /* OnboardingBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackgroundView.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; @@ -1193,7 +1203,7 @@ ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; @@ -2187,6 +2197,7 @@ 7583EAC171059A86B767209F /* MediaProvider */, 7DBC911559934065993A5FF4 /* NotificationManager */, 1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */, + A733C86D2A1E1C190055ECD6 /* TextBasedRoomTimelineTests.swift */, ); path = Sources; sourceTree = ""; @@ -2710,6 +2721,9 @@ F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */, A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */, 1941C8817E6B6971BA4415F5 /* VideoRoomTimelineView.swift */, + A733C86B2A1E149E0055ECD6 /* TextBasedRoomTimelineItem.swift */, + A7C152952A1F4E4C0089FF9D /* TextBasedRoomTimelineViewProtocol.swift */, + A7C152972A1F4E710089FF9D /* TextBasedRoomTimelineViewMock.swift */, ); path = Timeline; sourceTree = ""; @@ -2820,6 +2834,7 @@ children = ( D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */, 351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */, + A733C8692A1D17590055ECD6 /* TimelineReceiptView.swift */, ); path = Supplementary; sourceTree = ""; @@ -3516,6 +3531,7 @@ 864C69CF951BF36D25BE0C03 /* DeveloperOptionsScreenViewModelTests.swift in Sources */, 9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */, 501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */, + A733C86E2A1E1C190055ECD6 /* TextBasedRoomTimelineTests.swift in Sources */, 25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */, 71B62C48B8079D49F3FBC845 /* ExpiringTaskRunnerTests.swift in Sources */, CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */, @@ -3636,6 +3652,7 @@ B98A20A093A4FB785BFCCA53 /* BugReportScreenCoordinator.swift in Sources */, 4FFDC274824F7CC0BBDF581E /* BugReportScreenModels.swift in Sources */, 8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */, + A733C86C2A1E149E0055ECD6 /* TextBasedRoomTimelineItem.swift in Sources */, B4A0C69370E6008A971463E7 /* BugReportScreenViewModelProtocol.swift in Sources */, 3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */, 172E6E9A612ADCF10A62CF13 /* BugReportServiceProtocol.swift in Sources */, @@ -3872,6 +3889,7 @@ 77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */, CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */, B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */, + A7C152962A1F4E4C0089FF9D /* TextBasedRoomTimelineViewProtocol.swift in Sources */, 50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */, 0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */, 0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */, @@ -3936,6 +3954,8 @@ 157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */, 01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */, FBCCF1EA25A071324FCD8544 /* TimelineItemDebugView.swift in Sources */, + A733C86A2A1D17590055ECD6 /* TimelineReceiptView.swift in Sources */, + A7C152982A1F4E710089FF9D /* TextBasedRoomTimelineViewMock.swift in Sources */, F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */, 440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */, 9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */, diff --git a/ElementX/Sources/Other/Extensions/String.swift b/ElementX/Sources/Other/Extensions/String.swift index bd02b3bbc..26294887c 100644 --- a/ElementX/Sources/Other/Extensions/String.swift +++ b/ElementX/Sources/Other/Extensions/String.swift @@ -62,3 +62,22 @@ extension String { 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}" + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index 7f3232ce9..4d109ae2b 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -17,17 +17,20 @@ import Foundation import SwiftUI -struct TimelineItemBubbledStylerView: View { +struct TimelineItemBubbledStylerView: View { @EnvironmentObject private var context: RoomScreenViewModel.Context @Environment(\.timelineGroupStyle) private var timelineGroupStyle let timelineItem: EventBasedTimelineItemProtocol @ViewBuilder let content: () -> Content - @ViewBuilder let deliveryStatus: () -> DeliveryStatus @ScaledMetric private var senderNameVerticalPadding = 3 private let cornerRadius: CGFloat = 12 + private var isTextItem: Bool { + timelineItem is TextBasedRoomTimelineItem + } + var body: some View { ZStack(alignment: .trailingFirstTextBaseline) { VStack(alignment: alignment, spacing: -12) { @@ -83,7 +86,8 @@ struct TimelineItemBubbledStylerView: View } } - deliveryStatus() + TimelineReceiptView(timelineItem: timelineItem) + .environmentObject(context) .padding(.top, 10) .padding(.bottom, 3) } @@ -108,28 +112,48 @@ struct TimelineItemBubbledStylerView: View cornerRadius: cornerRadius, corners: roundedCorners) } else { - VStack(alignment: .trailing, spacing: 4) { - contentWithReply - - if timelineItem.properties.isEdited { - Text(L10n.commonEditedSuffix) - .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) + contentWithTimestamp + .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 var contentWithReply: some View { @@ -213,6 +237,9 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider { static var previews: some View { mockTimeline .previewDisplayName("Mock Timeline") + mockTimeline + .environment(\.layoutDirection, .rightToLeft) + .previewDisplayName("Mock Timeline RTL") replies .previewDisplayName("Replies") } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift index c77b7f18a..b27dd85a9 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift @@ -17,13 +17,12 @@ import Foundation import SwiftUI -struct TimelineItemPlainStylerView: View { +struct TimelineItemPlainStylerView: View { @EnvironmentObject private var context: RoomScreenViewModel.Context @Environment(\.timelineGroupStyle) private var timelineGroupStyle let timelineItem: EventBasedTimelineItemProtocol @ViewBuilder let content: () -> Content - @ViewBuilder let deliveryStatus: () -> DeliveryStatus var body: some View { VStack(alignment: .trailing) { @@ -39,7 +38,8 @@ struct TimelineItemPlainStylerView: View { supplementaryViews } } - deliveryStatus() + TimelineReceiptView(timelineItem: timelineItem) + .environmentObject(context) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyler.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyler.swift index 9b2da6f49..ad0440758 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyler.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyler.swift @@ -21,12 +21,6 @@ import SwiftUI struct TimelineStyler: View { @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 @ViewBuilder let content: () -> Content @@ -34,36 +28,9 @@ struct TimelineStyler: View { var body: some View { switch style { case .plain: - TimelineItemPlainStylerView(timelineItem: timelineItem, content: content) { - deliveryStatusView - } + TimelineItemPlainStylerView(timelineItem: timelineItem, content: content) case .bubbles: - 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 + TimelineItemBubbledStylerView(timelineItem: timelineItem, content: content) } } } @@ -71,7 +38,7 @@ struct TimelineStyler: View { struct TimelineItemStyler_Previews: PreviewProvider { 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 = { var result = base @@ -93,27 +60,17 @@ struct TimelineItemStyler_Previews: PreviewProvider { static let last: TextRoomTimelineItem = { 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 }() static var testView: some View { VStack { - TimelineStyler(timelineItem: sent) { - Text("Sent") - } - TimelineStyler(timelineItem: sending) { - Text("Sending") - } - TimelineStyler(timelineItem: base) { - Text("Normal") - } - TimelineStyler(timelineItem: last) { - Text("Last") - } - TimelineStyler(timelineItem: failed) { - Text("Failed") - } + TextRoomTimelineView(timelineItem: sent) + TextRoomTimelineView(timelineItem: sending) + TextRoomTimelineView(timelineItem: base) + TextRoomTimelineView(timelineItem: last) + TextRoomTimelineView(timelineItem: failed) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReceiptView.swift b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReceiptView.swift new file mode 100644 index 000000000..c6873c715 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReceiptView.swift @@ -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 + } + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift index 5308eebdd..7e0c8985f 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift @@ -17,15 +17,16 @@ import Foundation import SwiftUI -struct EmoteRoomTimelineView: View { +struct EmoteRoomTimelineView: View, TextBasedRoomTimelineViewProtocol { + @Environment(\.timelineStyle) var timelineStyle let timelineItem: EmoteRoomTimelineItem var body: some View { TimelineStyler(timelineItem: timelineItem) { if let attributedString = timelineItem.content.formattedBody { - FormattedBodyText(attributedString: attributedString) + FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces) } else { - FormattedBodyText(text: timelineItem.content.body) + FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: additionalWhitespaces) } } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift index 62d90a590..d08f3be64 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift @@ -18,11 +18,25 @@ import SwiftUI struct FormattedBodyText: View { @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) { - attributedComponents = attributedString.formattedComponents + init(attributedString: AttributedString, additionalWhitespacesCount: Int = 0) { + self.attributedString = attributedString + self.additionalWhitespacesCount = additionalWhitespacesCount + } + + // 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 { @@ -109,8 +123,8 @@ struct FormattedBodyText: View { } extension FormattedBodyText { - init(text: String) { - self.init(attributedString: AttributedString(text)) + init(text: String, additionalWhitespacesCount: Int = 0) { + self.init(attributedString: AttributedString(text), additionalWhitespacesCount: additionalWhitespacesCount) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift index 02af5592d..00ee435a1 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift @@ -17,8 +17,9 @@ import Foundation import SwiftUI -struct NoticeRoomTimelineView: View { +struct NoticeRoomTimelineView: View, TextBasedRoomTimelineViewProtocol { let timelineItem: NoticeRoomTimelineItem + @Environment(\.timelineStyle) var timelineStyle var body: some View { TimelineStyler(timelineItem: timelineItem) { @@ -32,9 +33,9 @@ struct NoticeRoomTimelineView: View { .foregroundColor(.element.secondaryContent) if let attributedString = timelineItem.content.formattedBody { - FormattedBodyText(attributedString: attributedString) + FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces) } else { - FormattedBodyText(text: timelineItem.content.body) + FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: additionalWhitespaces) } } .padding(.leading, 4) // Trailing padding is provided by FormattedBodyText diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineItem.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineItem.swift new file mode 100644 index 000000000..3345ab209 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineItem.swift @@ -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 + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineViewMock.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineViewMock.swift new file mode 100644 index 000000000..bb88ba6a7 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineViewMock.swift @@ -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: 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! +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineViewProtocol.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineViewProtocol.swift new file mode 100644 index 000000000..1d78c4074 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextBasedRoomTimelineViewProtocol.swift @@ -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 + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift index a0632cbd9..d1e09e637 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift @@ -17,15 +17,16 @@ import Foundation import SwiftUI -struct TextRoomTimelineView: View { +struct TextRoomTimelineView: View, TextBasedRoomTimelineViewProtocol { let timelineItem: TextRoomTimelineItem + @Environment(\.timelineStyle) var timelineStyle var body: some View { TimelineStyler(timelineItem: timelineItem) { if let attributedString = timelineItem.content.formattedBody { - FormattedBodyText(attributedString: attributedString) + FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: additionalWhitespaces) } else { - FormattedBodyText(text: timelineItem.content.body) + FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: additionalWhitespaces) } } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/EmoteRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/EmoteRoomTimelineItem.swift index 569c79bb1..3febb2bd3 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/EmoteRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/EmoteRoomTimelineItem.swift @@ -16,7 +16,7 @@ import UIKit -struct EmoteRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable { +struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable { let id: String let timestamp: String let isOutgoing: Bool diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/NoticeRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/NoticeRoomTimelineItem.swift index 1e40c3ea1..2cde6ecab 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/NoticeRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/NoticeRoomTimelineItem.swift @@ -16,7 +16,7 @@ import UIKit -struct NoticeRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable { +struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable { let id: String let timestamp: String let isOutgoing: Bool diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/TextRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/TextRoomTimelineItem.swift index 600edd632..ffb8b15cf 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/TextRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/TextRoomTimelineItem.swift @@ -16,7 +16,7 @@ import UIKit -struct TextRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable { +struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable { let id: String let timestamp: String diff --git a/UITests/Sources/RoomScreenUITests.swift b/UITests/Sources/RoomScreenUITests.swift index cc99ae1b3..a533864db 100644 --- a/UITests/Sources/RoomScreenUITests.swift +++ b/UITests/Sources/RoomScreenUITests.swift @@ -118,7 +118,7 @@ class RoomScreenUITests: XCTestCase { defer { try? client.stop() } // 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() } let cropped = UIEdgeInsets(top: 150, left: 0, bottom: 0, right: 0) // Ignore the navigation bar and pagination indicator as these change. diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomEncryptedWithAvatar.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomEncryptedWithAvatar.png index cbc2e51c6..e22604006 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomEncryptedWithAvatar.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomEncryptedWithAvatar.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e61e6dccf47027af9b1d9d40b28a38ab9b75b4455cfc4dd93835748caf095b7 -size 226144 +oid sha256:a4983811f9c93ae9d91957ad62b91456beed84048abbc78339ccd159edb9b6e9 +size 234145 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomLayoutTop.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomLayoutTop.png index a79d03a03..1dcc18221 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomLayoutTop.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomLayoutTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72f234c5fe48fb4a1faa736c34d35a9bb693c3d3fa2b729a09df0602ca072ef5 -size 266282 +oid sha256:a1ddd83f9f845b6d6281f8e291674957f9d3b2d6d5dfad66b3ead58cf3faa147 +size 278646 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomPlainNoAvatar.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomPlainNoAvatar.png index 0b01d0f93..1b7247ed4 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomPlainNoAvatar.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomPlainNoAvatar.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23c7126fd1fa69e482220b3a1e1fc3608b72e8a7dacb1f2abf5b8d5b95befb1b -size 226023 +oid sha256:b24385ffd83f5124614ec088dbcc96ac80b717eeba49b0e87624b308a6a00875 +size 234046 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimeline.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimeline.png index d38a9118a..314b114b7 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimeline.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimeline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1071fdafcad66664288bc18505f733829f045c07faecaeb36d62be896262a3a8 -size 96975 +oid sha256:d16782fe705cc6c2d5dcf0c5e60ee72898c599c8226e6fc3abdbe0b2f6ede5f7 +size 100181 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimelineIncomingAndSmallPagination.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimelineIncomingAndSmallPagination.png index 19ced1b50..16a435c4e 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimelineIncomingAndSmallPagination.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimelineIncomingAndSmallPagination.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f8de7ead20eef42d189b3582169783eaa037478ca43c9f1004f216abfc2b3e9 -size 116418 +oid sha256:863a56b98b4781de1da9122817c84df6791afd25defc3b9709737c9417e72481 +size 122963 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimelineLargePagination.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimelineLargePagination.png index 5c38bb968..a9ae16a0e 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimelineLargePagination.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomSmallTimelineLargePagination.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3204698079695ad6197fbdba23e1e45322f3981a202019bbedf44fda401dc4d -size 284896 +oid sha256:17578cec39bfddd1b1ff1963b74ce1e9863d073f133de441e0997a21c5bfdf37 +size 298067 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomEncryptedWithAvatar.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomEncryptedWithAvatar.png index 5953c4c79..97b6ace97 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomEncryptedWithAvatar.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomEncryptedWithAvatar.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:891bd87182b1ce91da99b4748b4c951eeea0f8cb81355628a3f6cf75d97c1e6a -size 350090 +oid sha256:e44ebb4db0febc50dd1ae363c106d149e4a8e6e0386eb7c6e2b6b442a3784535 +size 358633 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomLayoutTop.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomLayoutTop.png index 376d1802b..7b4589cd0 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomLayoutTop.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomLayoutTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71b1a302c3013f61056be9c3aad8d429aad25f402a38b7ad47fca32999b6e5d3 -size 248611 +oid sha256:81b08daf45dccae39110df315f14d0aeb5027b805af18bee10ba8fa561fb980a +size 246221 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomPlainNoAvatar.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomPlainNoAvatar.png index d4488907f..dff1b543f 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomPlainNoAvatar.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomPlainNoAvatar.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d5cf517ea15a9a5fdfcacc858b7b3173cdc1a9bda0afa975b4e271f4afdc0c9 -size 349837 +oid sha256:0160aea39bc6016c5e423d2d846f247d1f1e9d0024a1f5c833a98be7e77ea207 +size 358357 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimeline.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimeline.png index 326672123..ef98c1baa 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimeline.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimeline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7109c790683af45b2f29fd0a6aa8496ac09325e48a15a27d6abde5212139acad -size 150619 +oid sha256:9fc157e760cd2faf5936011d69f53744b7d037c9ee8f8434d071ff49e626e9da +size 155517 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimelineIncomingAndSmallPagination.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimelineIncomingAndSmallPagination.png index 879628462..83bcacfd0 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimelineIncomingAndSmallPagination.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimelineIncomingAndSmallPagination.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7c6bac06cdaa0b3f126eecbfe1cf232ac34707e065f13fbaf68708c58cf2293 -size 181693 +oid sha256:c849dbf18fda70046a9cc5e8a117078a69c7b5618222710c92f437b288217d74 +size 191841 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimelineLargePagination.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimelineLargePagination.png index 1303ca5a8..2a8f3db39 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimelineLargePagination.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomSmallTimelineLargePagination.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1961e6e76df75e7d3fcb5e385fa66f5d8895e0dc3a48d5b12d42ef37253488ec -size 302074 +oid sha256:77e6a585c3e6057cbb2c92f7b525b1464c13570a67b809ba6c02a4f09b7e0a60 +size 318416 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomEncryptedWithAvatar.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomEncryptedWithAvatar.png index e3cb9c204..5b53790ae 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomEncryptedWithAvatar.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomEncryptedWithAvatar.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ee1d5f6d28f2edc5fa5446e8e9df6268df8e543dd83230f05001f6a502b54da -size 227470 +oid sha256:845e339376c379882e2a4811a4349cb89377abd348282a76b29640ec3ed5e551 +size 235185 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomLayoutTop.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomLayoutTop.png index 500062189..d0551b79a 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomLayoutTop.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomLayoutTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c80d586a3bac65665769abdaea9bd94819eb0e5796aa5b84d668638124e95b73 -size 266691 +oid sha256:5460a8e7171dccc726fc7a5da386d0408f86998ea4d51b06b05c90f077b8bc21 +size 279028 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomPlainNoAvatar.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomPlainNoAvatar.png index 48576d5be..d1f97c8a1 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomPlainNoAvatar.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomPlainNoAvatar.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e24bd90d4861049fd1473e81e237f35253063544fa5205ead132566fa3f4c76 -size 227347 +oid sha256:f7b7650aab00b11152a22a63721b2005364d1e915c44cd802a29ced4f5f586ab +size 235091 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimeline.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimeline.png index 7a6b55382..341f72608 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimeline.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimeline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7590bf3ce258bd1ed83674c7aa8fe029cc6b65cc789e60db6c5aac75f88de1c5 -size 97468 +oid sha256:d413f50e7778b19fdb8baac0e11b6f78c76af3b58436e405a6f60a9b42a50940 +size 100669 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimelineIncomingAndSmallPagination.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimelineIncomingAndSmallPagination.png index 16691af4c..2abc461e4 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimelineIncomingAndSmallPagination.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimelineIncomingAndSmallPagination.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aff34dd5e386da4c9491eb19bcb6e2ecd40301640be3768f04f40b2f447dcda2 -size 116895 +oid sha256:283db285833ed3fd65ae6adf61111ffca3f3a9d5fc3fb05d240f81fac7ea8b17 +size 123454 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimelineLargePagination.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimelineLargePagination.png index d6ed7fed7..f449a5130 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimelineLargePagination.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.roomSmallTimelineLargePagination.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55e1b3c880fc7589c9d955605e16d3df33d842d37fff91efd31acc787d817ee6 -size 285371 +oid sha256:3b7fb769b14c81ff5e9d9be36083ffa48623c90ea4d333105c071bf326d0c859 +size 298553 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomEncryptedWithAvatar.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomEncryptedWithAvatar.png index 0004fc031..a7033e66b 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomEncryptedWithAvatar.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomEncryptedWithAvatar.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:270122db18781f10a4357610bec368381c8977f36cd81ebabd16ab8abefae822 -size 350678 +oid sha256:a2ab1da0274529f2bc26af0b7d9d94ad399a5c46e6f3a496df3885d1cd650729 +size 332000 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomLayoutTop.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomLayoutTop.png index 97b27537b..dfb51267a 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomLayoutTop.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomLayoutTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fb535d6c0766d034d32cf2488acbee08053d620dec8f33570506073eca44af7 -size 249202 +oid sha256:8f2115478483a5e24fa09118e00efc8eedb79db01782b7a54ef0e70c5baf27b3 +size 239702 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomPlainNoAvatar.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomPlainNoAvatar.png index 2b90c6f6e..ef0d1bf5b 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomPlainNoAvatar.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomPlainNoAvatar.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d11402df691d0fce38b74683a8e3e21915ad592ce8ef9a72c6a0947e8a5af30d -size 350425 +oid sha256:b4764bac791d16b80ab2a2303e97bad7eb5971b45439319b8f633752718279cd +size 331646 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimeline.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimeline.png index d3ff6369f..6a7401fff 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimeline.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimeline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8feb34445e1fcb0d2fe2061a225428f5658436a8821a722b31fefb6e0063f7c8 -size 151125 +oid sha256:a1ef9a0b33c1c8c9fbae6fc66f0d9bbdbda390574539c6c2bfc94c1c8e3ac81a +size 156266 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimelineIncomingAndSmallPagination.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimelineIncomingAndSmallPagination.png index 9ca06ab60..abc67f26b 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimelineIncomingAndSmallPagination.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimelineIncomingAndSmallPagination.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26051b89f610c6d5810559df462951cbe4f82ba0dba02568d27052ec7ce895f1 -size 182242 +oid sha256:8fc2fcf3937665fe3a581c9378d023ca57f4d5e630eb51496d7f21a1e4fe9a8f +size 192441 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimelineLargePagination.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimelineLargePagination.png index cac8a3ddf..ef50591b1 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimelineLargePagination.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.roomSmallTimelineLargePagination.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7484ae40068b388b98e271f962529e44c67b514c2e8a7bac934bfbbfeb4b9903 -size 302656 +oid sha256:643fdf24fa8f7553f120da069ddbfe13d5b2e2405ad2b0041ad765b0efd99797 +size 305866 diff --git a/UnitTests/Sources/StringTests.swift b/UnitTests/Sources/StringTests.swift index 43e742077..254256f81 100644 --- a/UnitTests/Sources/StringTests.swift +++ b/UnitTests/Sources/StringTests.swift @@ -58,4 +58,26 @@ class StringTests: XCTestCase { func testStripsTheHeartbreakHotel() { 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) + } } diff --git a/UnitTests/Sources/TextBasedRoomTimelineTests.swift b/UnitTests/Sources/TextBasedRoomTimelineTests.swift new file mode 100644 index 000000000..321d4c4f5 --- /dev/null +++ b/UnitTests/Sources/TextBasedRoomTimelineTests.swift @@ -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() + 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() + 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() + 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() + 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() + view.underlyingTimelineItem = timelineItem + view.timelineStyle = .bubbles + + XCTAssertEqual(view.additionalWhitespaces, timestamp.count + editedCount + 5) + } +} diff --git a/changelog.d/948.feature b/changelog.d/948.feature new file mode 100644 index 000000000..eedb87b24 --- /dev/null +++ b/changelog.d/948.feature @@ -0,0 +1 @@ +Timestamp for messages incorporated in a bubble. \ No newline at end of file