From 704b3dbb29607f79a6498e93ad6625021f08ff7f Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Wed, 29 Jul 2026 16:28:46 +0200 Subject: [PATCH] Render replies to gallery items (#5942) --- .../View/Replies/TimelineReplyView.swift | 67 +++++++++++++++++-- .../timelineReplyView.iPad-en-GB-0.png | 4 +- .../timelineReplyView.iPad-pseudo-0.png | 4 +- .../timelineReplyView.iPhone-en-GB-0.png | 4 +- .../timelineReplyView.iPhone-pseudo-0.png | 4 +- 5 files changed, 69 insertions(+), 14 deletions(-) diff --git a/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift b/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift index 63717b2f0..989adb9b0 100644 --- a/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift @@ -60,6 +60,8 @@ struct TimelineReplyView: View { case .image(let content): return content.imageInfo.source case .video(let content): return content.videoInfo.source case .voice(let content): return content.source + // Only the first item is scanned as it's the only one the reply previews. + case .gallery(let content): return content.items.first?.mediaSource default: return nil } } @@ -75,6 +77,7 @@ struct TimelineReplyView: View { case .file(let content): return content.thumbnailSource case .image(let content): return content.thumbnailInfo?.source case .video(let content): return content.thumbnailInfo?.source + case .gallery(let content): return content.items.first?.thumbnailSource default: return nil } } @@ -130,12 +133,7 @@ struct TimelineReplyView: View { formattedBody: nil, icon: .init(kind: .icon(\.locationPin))) case .gallery(let content): - ReplyView(sender: sender, - plainBody: content.caption ?? content.body, - formattedBody: content.formattedCaption, - icon: content.items.first?.thumbnailSource.map { .init(kind: .mediaSource($0)) } - ?? content.items.first?.mediaSource.map { .init(kind: .mediaSource($0)) } - ?? .init(kind: .icon(\.attachment))) + GalleryReplyView(sender: sender, content: content) } case .poll(let question): ReplyView(sender: sender, @@ -159,6 +157,47 @@ struct TimelineReplyView: View { } } + /// A gallery previews its first attachment as though it had been sent on its own, counting its + /// media when they're all images/videos and its attachments otherwise. + private struct GalleryReplyView: View { + let sender: TimelineItemSender + let content: GalleryRoomTimelineItemContent + + private var caption: String? { + content.caption?.isBlank == false ? content.caption : nil + } + + private var isMediaGallery: Bool { + content.items.allSatisfy { $0.isImage || $0.isVideo } + } + + private var placeholder: String { + if isMediaGallery { + L10n.commonGalleryReplyMediaItems(content.items.count) + } else { + L10n.commonGalleryReplyAttachments(content.items.count) + } + } + + private var icon: ReplyView.Icon? { + guard let item = content.items.first else { return nil } + + return switch item { + case .image(_, let itemContent): .init(kind: .mediaSource(itemContent.thumbnailInfo?.source ?? itemContent.imageInfo.source)) + case .video(_, let itemContent): itemContent.thumbnailInfo.map { .init(kind: .mediaSource($0.source)) } + case .audio: .init(kind: .icon(\.audio)) + case .file, .other: .init(kind: .icon(\.attachment)) + } + } + + var body: some View { + ReplyView(sender: sender, + plainBody: caption ?? placeholder, + formattedBody: caption == nil ? nil : content.formattedCaption, + icon: icon) + } + } + private struct LoadingReplyView: View { var body: some View { ReplyView(sender: .init(id: "@alice:matrix.org"), plainBody: "Hello world", formattedBody: nil) @@ -402,6 +441,22 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview { timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bob"), eventID: "123", eventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithEventOnRoomAliasMention))))), + TimelineReplyView(placement: .timeline, + timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), + eventID: "123", + eventContent: .message(.gallery(.init(body: "Gallery", + items: [.mockImage(index: 0), + .mockVideo(index: 1)]))))), + + TimelineReplyView(placement: .timeline, + timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), + eventID: "123", + eventContent: .message(.gallery(.init(body: "Gallery", + caption: "A trip to remember 🌅", + items: [.mockImage(index: 0), + .mockVideo(index: 1), + .mockImage(index: 2)]))))), + TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bob"), eventID: "123", diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPad-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPad-en-GB-0.png index 9443d65cd..61d3dc33c 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPad-en-GB-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPad-en-GB-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90589b29a858d016ef4e9b22951f58b7cc1e91b101d1190891e557fa8ceb9547 -size 271039 +oid sha256:48e2c170447a62a56f085126489c5ed819ee3377e2efc29502da4e7741bf3288 +size 330696 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPad-pseudo-0.png index a84277fb8..d06d3bd40 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPad-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPad-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a119dbafa20c81937c68f725af6d4f0029647cd043264e6bf912cb0a10b0c0a7 -size 280754 +oid sha256:b30099d8d6e152af0c21f127bafe0f67ecb6552c983cafcaf3a942ad306b2ea5 +size 345232 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPhone-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPhone-en-GB-0.png index fa2d47a80..67a9db30c 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPhone-en-GB-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPhone-en-GB-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f4a10b76481e53d8f58810df4c2073f2ad71041712d10efbd2cff6cc67a548f -size 192308 +oid sha256:4ee06e1dc82d0d4261ddce83611fa44275e87bee6117a5e4e1474e9165af5df3 +size 242630 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPhone-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPhone-pseudo-0.png index 8ddb2bdbb..3317723c9 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPhone-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineReplyView.iPhone-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd00fd4e7354c7e840b67cce353ad4a157eea52060b80f7bac6e00aabf32936c -size 192329 +oid sha256:bc701056fe46fe1c5f3c12e9698a2f95b79057d931b29060603b13bd2233df46 +size 245641