From 9e2586d5feba457eb06b9b407b1884d8204f5b32 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 18 Oct 2024 16:45:41 +0100 Subject: [PATCH] Add support for rendering media captions in the timeline. --- .../Style/TimelineItemBubbledStylerView.swift | 5 +- .../Style/TimelineItemSendInfoLabel.swift | 6 +-- .../ImageRoomTimelineView.swift | 52 +++++++++++++++---- .../VideoRoomTimelineView.swift | 40 ++++++++++++-- ...ventBasedMessageTimelineItemProtocol.swift | 17 ++++++ ...est_imageRoomTimelineView-iPad-en-GB.1.png | 4 +- ...st_imageRoomTimelineView-iPad-pseudo.1.png | 4 +- ...mageRoomTimelineView-iPhone-16-en-GB.1.png | 4 +- ...ageRoomTimelineView-iPhone-16-pseudo.1.png | 4 +- ...est_videoRoomTimelineView-iPad-en-GB.1.png | 4 +- ...st_videoRoomTimelineView-iPad-pseudo.1.png | 4 +- ...ideoRoomTimelineView-iPhone-16-en-GB.1.png | 4 +- ...deoRoomTimelineView-iPhone-16-pseudo.1.png | 4 +- 13 files changed, 116 insertions(+), 36 deletions(-) diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift index 63e2304ba2..0d3fc5360e 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift @@ -257,7 +257,7 @@ private extension EventBasedTimelineItemProtocol { switch self { case is ImageRoomTimelineItem, is VideoRoomTimelineItem: // In case a reply detail or a thread decorator is present we render the color and the padding - return self.replyDetails != nil || self.isThreaded ? defaultColor : nil + return self.replyDetails != nil || self.isThreaded || self.hasMediaCaption ? defaultColor : nil default: return defaultColor } @@ -283,8 +283,7 @@ private extension EventBasedTimelineItemProtocol { // In case a reply detail or a thread decorator is present we render the color and the padding case is ImageRoomTimelineItem, is VideoRoomTimelineItem: - return self.replyDetails != nil || - self.isThreaded ? defaultInsets : .zero + return self.replyDetails != nil || self.isThreaded || self.hasMediaCaption ? defaultInsets : .zero case let locationTimelineItem as LocationRoomTimelineItem: return locationTimelineItem.content.geoURI == nil || self.replyDetails != nil || diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemSendInfoLabel.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemSendInfoLabel.swift index d2cbd56391..b350a9b2e7 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemSendInfoLabel.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemSendInfoLabel.swift @@ -152,9 +152,9 @@ private extension TimelineItemSendInfo { layoutType = switch timelineItem { case is TextBasedRoomTimelineItem: .overlay(capsuleStyle: false) - case is ImageRoomTimelineItem, - is VideoRoomTimelineItem, - is StickerRoomTimelineItem: + case let message as EventBasedMessageTimelineItemProtocol where message is ImageRoomTimelineItem || message is VideoRoomTimelineItem: + .overlay(capsuleStyle: !message.hasMediaCaption) + case is StickerRoomTimelineItem: .overlay(capsuleStyle: true) case let locationTimelineItem as LocationRoomTimelineItem: .overlay(capsuleStyle: locationTimelineItem.content.geoURI != nil) diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift index e86d30f13f..63cb9b04ef 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift @@ -12,18 +12,35 @@ struct ImageRoomTimelineView: View { @EnvironmentObject private var context: TimelineViewModel.Context let timelineItem: ImageRoomTimelineItem + var hasMediaCaption: Bool { timelineItem.content.caption != nil } + var body: some View { TimelineStyler(timelineItem: timelineItem) { - LoadableImage(mediaSource: source, - mediaType: .timelineItem, - blurhash: timelineItem.content.blurhash, - mediaProvider: context.mediaProvider) { - placeholder + VStack(alignment: .leading, spacing: 4) { + LoadableImage(mediaSource: source, + mediaType: .timelineItem, + blurhash: timelineItem.content.blurhash, + mediaProvider: context.mediaProvider) { + placeholder + } + .timelineMediaFrame(height: timelineItem.content.height, + aspectRatio: timelineItem.content.aspectRatio) + .accessibilityElement(children: .ignore) + .accessibilityLabel(L10n.commonImage) + // This clip shape is distinct from the one in the styler as that one + // operates on the entire message so wouldn't round the bottom corners. + .clipShape(RoundedRectangle(cornerRadius: hasMediaCaption ? 6 : 0)) + + if let attributedCaption = timelineItem.content.formattedCaption { + FormattedBodyText(attributedString: attributedCaption, + additionalWhitespacesCount: timelineItem.additionalWhitespaces(), + boostEmojiSize: true) + } else if let caption = timelineItem.content.caption { + FormattedBodyText(text: caption, + additionalWhitespacesCount: timelineItem.additionalWhitespaces(), + boostEmojiSize: true) + } } - .timelineMediaFrame(height: timelineItem.content.height, - aspectRatio: timelineItem.content.aspectRatio) - .accessibilityElement(children: .ignore) - .accessibilityLabel(L10n.commonImage) } } @@ -87,6 +104,23 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { aspectRatio: 0.7, blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW", contentType: .gif))) + + ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .randomEvent, + timestamp: "Now", + isOutgoing: false, + isEditable: false, + canBeRepliedTo: true, + isThreaded: false, + sender: .init(id: "Bob"), + content: .init(filename: "Blurhashed.jpg", + caption: "This is a great image 😎", + source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), + thumbnailSource: nil, + width: 50, + height: 50, + aspectRatio: 1, + blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW", + contentType: .gif))) } } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift index a99980d85b..27b0ddd69e 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift @@ -12,13 +12,30 @@ struct VideoRoomTimelineView: View { @EnvironmentObject private var context: TimelineViewModel.Context let timelineItem: VideoRoomTimelineItem + private var hasMediaCaption: Bool { timelineItem.content.caption != nil } + var body: some View { TimelineStyler(timelineItem: timelineItem) { - thumbnail - .timelineMediaFrame(height: timelineItem.content.height, - aspectRatio: timelineItem.content.aspectRatio) - .accessibilityElement(children: .ignore) - .accessibilityLabel(L10n.commonVideo) + VStack(alignment: .leading, spacing: 4) { + thumbnail + .timelineMediaFrame(height: timelineItem.content.height, + aspectRatio: timelineItem.content.aspectRatio) + .accessibilityElement(children: .ignore) + .accessibilityLabel(L10n.commonVideo) + // This clip shape is distinct from the one in the styler as that one + // operates on the entire message so wouldn't round the bottom corners. + .clipShape(RoundedRectangle(cornerRadius: hasMediaCaption ? 6 : 0)) + + if let attributedCaption = timelineItem.content.formattedCaption { + FormattedBodyText(attributedString: attributedCaption, + additionalWhitespacesCount: timelineItem.additionalWhitespaces(), + boostEmojiSize: true) + } else if let caption = timelineItem.content.caption { + FormattedBodyText(text: caption, + additionalWhitespacesCount: timelineItem.additionalWhitespaces(), + boostEmojiSize: true) + } + } } } @@ -100,6 +117,19 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { thumbnailSource: nil, aspectRatio: 0.7, blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW"))) + + VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .randomEvent, + timestamp: "Now", + isOutgoing: false, + isEditable: false, + canBeRepliedTo: true, + isThreaded: false, + sender: .init(id: "Bob"), + content: .init(filename: "video.mp4", + caption: "This is a caption", + duration: 21, + source: nil, + thumbnailSource: nil))) } } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedMessageTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedMessageTimelineItemProtocol.swift index ce91fe8df0..6b4ae9f5ea 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedMessageTimelineItemProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedMessageTimelineItemProtocol.swift @@ -24,3 +24,20 @@ protocol EventBasedMessageTimelineItemProtocol: EventBasedTimelineItemProtocol { var contentType: EventBasedMessageTimelineItemContentType { get } var isThreaded: Bool { get } } + +extension EventBasedMessageTimelineItemProtocol { + var hasMediaCaption: Bool { + switch contentType { + case .audio(let content): + content.caption != nil + case .file(let content): + content.caption != nil + case .image(let content): + content.caption != nil + case .video(let content): + content.caption != nil + case .emote, .notice, .text, .location, .voice: + false + } + } +} diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPad-en-GB.1.png index 5469ed0e38..df4ec92cfe 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f63e72b33f82f8b679dd8b1fc810078438914d70d395c23abd13571d01643597 -size 227546 +oid sha256:6cf2fd3de65756ae06e150b5353386e9b5bb554d18da2f77b0cf54246c3920eb +size 285656 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPad-pseudo.1.png index 5469ed0e38..df4ec92cfe 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f63e72b33f82f8b679dd8b1fc810078438914d70d395c23abd13571d01643597 -size 227546 +oid sha256:6cf2fd3de65756ae06e150b5353386e9b5bb554d18da2f77b0cf54246c3920eb +size 285656 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPhone-16-en-GB.1.png index bd08a52887..70f26febce 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb0bce1bf5571926c9ae760503e3aff11bfa962d646e257ab9481bc9496b95a6 -size 169079 +oid sha256:0b2d7c9d9bf570589eb48fbbb11bf11f2970229599a5be9b06d3f54e855e0d06 +size 224889 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPhone-16-pseudo.1.png index bd08a52887..70f26febce 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageRoomTimelineView-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb0bce1bf5571926c9ae760503e3aff11bfa962d646e257ab9481bc9496b95a6 -size 169079 +oid sha256:0b2d7c9d9bf570589eb48fbbb11bf11f2970229599a5be9b06d3f54e855e0d06 +size 224889 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPad-en-GB.1.png index 1bc9dd26dc..18df7f1a0b 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:919c37cec762f3a5ead4e55e611e78bc3b6a52fdb6c2f2435d387511bdaf3bb3 -size 81177 +oid sha256:f0f9e472dd0a7675c25bc5856aca8f3e8ed94b157aa2dea3683e8a7cf17b588c +size 94892 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPad-pseudo.1.png index 1bc9dd26dc..18df7f1a0b 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:919c37cec762f3a5ead4e55e611e78bc3b6a52fdb6c2f2435d387511bdaf3bb3 -size 81177 +oid sha256:f0f9e472dd0a7675c25bc5856aca8f3e8ed94b157aa2dea3683e8a7cf17b588c +size 94892 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPhone-16-en-GB.1.png index 15d62f2135..d3bd5b56d3 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbf8a9e38c8188356a7e9758b7afbcbc84c4b58b4ada32999e1591883c99704c -size 40312 +oid sha256:8e4e25d87b4335854f8cd67072ef0e8e89a50a9a56dbc10b6cf01ee725731767 +size 52197 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPhone-16-pseudo.1.png index 15d62f2135..d3bd5b56d3 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoRoomTimelineView-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbf8a9e38c8188356a7e9758b7afbcbc84c4b58b4ada32999e1591883c99704c -size 40312 +oid sha256:8e4e25d87b4335854f8cd67072ef0e8e89a50a9a56dbc10b6cf01ee725731767 +size 52197