Add support for rendering media captions in the timeline. (#3429)

This commit is contained in:
Doug
2024-10-21 15:29:03 +01:00
committed by GitHub
parent 201e3b0c9c
commit a0c81cf393
13 changed files with 116 additions and 36 deletions
@@ -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 ||
@@ -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)
@@ -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)))
}
}
}
@@ -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)))
}
}
}
@@ -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
}
}
}
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f63e72b33f82f8b679dd8b1fc810078438914d70d395c23abd13571d01643597
size 227546
oid sha256:6cf2fd3de65756ae06e150b5353386e9b5bb554d18da2f77b0cf54246c3920eb
size 285656
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f63e72b33f82f8b679dd8b1fc810078438914d70d395c23abd13571d01643597
size 227546
oid sha256:6cf2fd3de65756ae06e150b5353386e9b5bb554d18da2f77b0cf54246c3920eb
size 285656
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fb0bce1bf5571926c9ae760503e3aff11bfa962d646e257ab9481bc9496b95a6
size 169079
oid sha256:0b2d7c9d9bf570589eb48fbbb11bf11f2970229599a5be9b06d3f54e855e0d06
size 224889
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fb0bce1bf5571926c9ae760503e3aff11bfa962d646e257ab9481bc9496b95a6
size 169079
oid sha256:0b2d7c9d9bf570589eb48fbbb11bf11f2970229599a5be9b06d3f54e855e0d06
size 224889
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:919c37cec762f3a5ead4e55e611e78bc3b6a52fdb6c2f2435d387511bdaf3bb3
size 81177
oid sha256:f0f9e472dd0a7675c25bc5856aca8f3e8ed94b157aa2dea3683e8a7cf17b588c
size 94892
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:919c37cec762f3a5ead4e55e611e78bc3b6a52fdb6c2f2435d387511bdaf3bb3
size 81177
oid sha256:f0f9e472dd0a7675c25bc5856aca8f3e8ed94b157aa2dea3683e8a7cf17b588c
size 94892
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bbf8a9e38c8188356a7e9758b7afbcbc84c4b58b4ada32999e1591883c99704c
size 40312
oid sha256:8e4e25d87b4335854f8cd67072ef0e8e89a50a9a56dbc10b6cf01ee725731767
size 52197
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bbf8a9e38c8188356a7e9758b7afbcbc84c4b58b4ada32999e1591883c99704c
size 40312
oid sha256:8e4e25d87b4335854f8cd67072ef0e8e89a50a9a56dbc10b6cf01ee725731767
size 52197