From ef288a9662142fca4785893a5fc8d62c4a93a483 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 15 Nov 2024 15:00:27 +0200 Subject: [PATCH] Group image and video metadata in specialised structs (#3518) --- .../SwiftUI/Layout/TimelineMediaFrame.swift | 10 +- .../View/MessageComposer.swift | 9 +- .../Timeline/TimelineInteractionHandler.swift | 4 +- .../View/Replies/TimelineReplyView.swift | 19 ++- .../Style/TimelineItemBubbledStylerView.swift | 8 +- .../ImageRoomTimelineView.swift | 33 +++-- .../StickerRoomTimelineView.swift | 14 +-- .../VideoRoomTimelineView.swift | 28 ++--- .../Fixtures/RoomTimelineItemFixtures.swift | 13 +- .../Services/Timeline/TimelineItemProxy.swift | 108 +++++++++++++++- .../ImageRoomTimelineItemContent.swift | 8 +- .../VideoRoomTimelineItemContent.swift | 10 +- .../Items/Other/StickerRoomTimelineItem.swift | 4 +- .../RoomTimelineItemFactory.swift | 117 ++++++------------ ...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 +- ...t_stickerRoomTimelineView-iPad-en-GB.1.png | 4 +- ..._stickerRoomTimelineView-iPad-pseudo.1.png | 4 +- ...ckerRoomTimelineView-iPhone-16-en-GB.1.png | 4 +- ...kerRoomTimelineView-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 +- UnitTests/Sources/LoggingTests.swift | 9 +- 27 files changed, 230 insertions(+), 212 deletions(-) diff --git a/ElementX/Sources/Other/SwiftUI/Layout/TimelineMediaFrame.swift b/ElementX/Sources/Other/SwiftUI/Layout/TimelineMediaFrame.swift index 1cec148401..c715f5fc93 100644 --- a/ElementX/Sources/Other/SwiftUI/Layout/TimelineMediaFrame.swift +++ b/ElementX/Sources/Other/SwiftUI/Layout/TimelineMediaFrame.swift @@ -10,18 +10,18 @@ import SwiftUI extension View { /// Constrains the max height of a media item in the timeline, whilst preserving its aspect ratio. @ViewBuilder - func timelineMediaFrame(height contentHeight: CGFloat?, aspectRatio contentAspectRatio: CGFloat?) -> some View { + func timelineMediaFrame(imageInfo: ImageInfoProxy?) -> some View { let defaultMediaSize = 100.0 let minMediaHeight = 100.0 let maxMediaHeight = 300.0 - if let contentHeight, contentHeight < minMediaHeight { // Special case very small images - aspectRatio(contentAspectRatio, contentMode: .fit) + if let contentHeight = imageInfo?.size?.height, contentHeight < minMediaHeight { // Special case very small images + aspectRatio(imageInfo?.aspectRatio, contentMode: .fit) .frame(minHeight: minMediaHeight, maxHeight: minMediaHeight) } else { - if let contentAspectRatio { + if let contentAspectRatio = imageInfo?.aspectRatio { aspectRatio(contentAspectRatio, contentMode: .fit) - .frame(maxHeight: min(maxMediaHeight, max(minMediaHeight, contentHeight ?? .infinity))) + .frame(maxHeight: min(maxMediaHeight, max(minMediaHeight, imageInfo?.size?.height ?? .infinity))) // Required to prevent the reply details to get higher priority in rendering the width of the view. .aspectRatio(contentAspectRatio, contentMode: .fit) } else { // Otherwise force the image to be `defaultMediaSize` x `defaultMediaSize` diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MessageComposer.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MessageComposer.swift index 0a142b37a1..809c80279f 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MessageComposer.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MessageComposer.swift @@ -226,8 +226,8 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview { eventID: "123", eventContent: .message(.image(.init(filename: "head.png", caption: "Image: Pushead", - source: .init(url: .picturesDirectory, mimeType: nil), - thumbnailSource: .init(url: .picturesDirectory, mimeType: nil))))), + imageInfo: .mockImage, + thumbnailInfo: .mockThumbnail)))), .loaded(sender: .init(id: "Jason"), eventID: "123", eventContent: .message(.notice(.init(body: "Notice: Too far gone?")))), @@ -238,9 +238,8 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview { eventID: "123", eventContent: .message(.video(.init(filename: "never.mov", caption: "Video: Through the never", - duration: 100, - source: nil, - thumbnailSource: .init(url: .picturesDirectory, mimeType: nil))))), + videoInfo: .mockVideo, + thumbnailInfo: .mockThumbnail)))), .loading(eventID: "") ] diff --git a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift index 83b2bd23dd..a70c982758 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift @@ -553,11 +553,11 @@ class TimelineInteractionHandler { switch timelineItem { case let item as ImageRoomTimelineItem: - source = item.content.source + source = item.content.imageInfo.source filename = item.content.filename caption = item.content.caption case let item as VideoRoomTimelineItem: - source = item.content.source + source = item.content.videoInfo.source filename = item.content.filename caption = item.content.caption case let item as FileRoomTimelineItem: diff --git a/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift b/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift index 6326e84a32..bd82866912 100644 --- a/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift @@ -42,7 +42,7 @@ struct TimelineReplyView: View { ReplyView(sender: sender, plainBody: content.caption ?? content.filename, formattedBody: content.formattedCaption, - icon: .init(kind: .mediaSource(content.thumbnailSource ?? content.source), cornerRadii: iconCornerRadii)) + icon: .init(kind: .mediaSource(content.thumbnailInfo?.source ?? content.imageInfo.source), cornerRadii: iconCornerRadii)) case .notice(let content): ReplyView(sender: sender, plainBody: content.body, @@ -55,7 +55,7 @@ struct TimelineReplyView: View { ReplyView(sender: sender, plainBody: content.caption ?? content.filename, formattedBody: content.formattedCaption, - icon: content.thumbnailSource.map { .init(kind: .mediaSource($0), cornerRadii: iconCornerRadii) }) + icon: content.thumbnailInfo.map { .init(kind: .mediaSource($0.source), cornerRadii: iconCornerRadii) }) case .voice: ReplyView(sender: sender, plainBody: L10n.commonVoiceMessage, @@ -222,9 +222,7 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview { }() static var previewItems: [TimelineReplyView] { - let imageSource = MediaSourceProxy(url: "https://mock.com", mimeType: "image/png") - - return [ + [ TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .notLoaded(eventID: "")), TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loading(eventID: "")), @@ -268,17 +266,16 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview { eventID: "123", eventContent: .message(.image(.init(filename: "image.jpg", caption: "Some image", - source: imageSource, - thumbnailSource: imageSource))))), + imageInfo: .mockImage, + thumbnailInfo: .mockThumbnail))))), TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), eventID: "123", eventContent: .message(.video(.init(filename: "video.mp4", caption: "Some video", - duration: 0, - source: nil, - thumbnailSource: imageSource))))), + videoInfo: .mockVideo, + thumbnailInfo: .mockThumbnail))))), TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), eventID: "123", @@ -321,7 +318,7 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview { } .padding() .environmentObject(viewModel.context) - // Allow member names to load. Reduce precission as the `imageSource` randomly renders slightly differently + // Allow member names to load. Reduce precission as the `mockThumbnail` randomly renders slightly differently .snapshotPreferences(delay: 0.2, precision: 0.98) .previewLayout(.sizeThatFits) } diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift index 61ed78ee82..e1b025cec9 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift @@ -498,8 +498,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview isThreaded: false, sender: .init(id: "Bob"), content: .init(filename: "other.png", - source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), - thumbnailSource: nil), + imageInfo: .mockImage, + thumbnailInfo: nil), properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray)))) @@ -577,8 +577,8 @@ private struct MockTimelineContent: View { isThreaded: isThreaded, sender: .init(id: ""), content: .init(filename: "image.jpg", - source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), - thumbnailSource: nil), + imageInfo: .mockImage, + thumbnailInfo: nil), replyDetails: replyDetails)) LocationRoomTimelineView(timelineItem: .init(id: makeItemIdentifier(), diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift index 2047c0ccef..beb06037db 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift @@ -40,25 +40,23 @@ struct ImageRoomTimelineView: View { @ViewBuilder private var loadableImage: some View { if timelineItem.content.contentType == .gif { - LoadableImage(mediaSource: timelineItem.content.source, + LoadableImage(mediaSource: timelineItem.content.imageInfo.source, mediaType: .timelineItem, blurhash: timelineItem.content.blurhash, - size: timelineItem.content.size, + size: timelineItem.content.imageInfo.size, mediaProvider: context.mediaProvider) { placeholder } - .timelineMediaFrame(height: timelineItem.content.thumbnailSize?.height, - aspectRatio: timelineItem.content.aspectRatio) + .timelineMediaFrame(imageInfo: timelineItem.content.imageInfo) } else { - LoadableImage(mediaSource: timelineItem.content.thumbnailSource ?? timelineItem.content.source, + LoadableImage(mediaSource: timelineItem.content.thumbnailInfo?.source ?? timelineItem.content.imageInfo.source, mediaType: .timelineItem, blurhash: timelineItem.content.blurhash, - size: timelineItem.content.thumbnailSize ?? timelineItem.content.size, + size: timelineItem.content.thumbnailInfo?.size ?? timelineItem.content.imageInfo.size, mediaProvider: context.mediaProvider) { placeholder } - .timelineMediaFrame(height: timelineItem.content.thumbnailSize?.height ?? timelineItem.content.size?.height, - aspectRatio: timelineItem.content.thumbnailAspectRatio ?? timelineItem.content.aspectRatio) + .timelineMediaFrame(imageInfo: timelineItem.content.thumbnailInfo ?? timelineItem.content.imageInfo) } } @@ -87,8 +85,8 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { isThreaded: false, sender: .init(id: "Bob"), content: .init(filename: "image.jpg", - source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/jpg"), - thumbnailSource: nil))) + imageInfo: .mockImage, + thumbnailInfo: nil))) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .randomEvent, timestamp: "Now", @@ -98,8 +96,8 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { isThreaded: false, sender: .init(id: "Bob"), content: .init(filename: "other.png", - source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), - thumbnailSource: nil))) + imageInfo: .mockImage, + thumbnailInfo: nil))) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .randomEvent, timestamp: "Now", @@ -109,9 +107,8 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { isThreaded: false, sender: .init(id: "Bob"), content: .init(filename: "Blurhashed.jpg", - source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/gif"), - aspectRatio: 0.7, - thumbnailSource: nil, + imageInfo: .mockImage, + thumbnailInfo: nil, blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW", contentType: .gif))) @@ -124,10 +121,8 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { sender: .init(id: "Bob"), content: .init(filename: "Blurhashed.jpg", caption: "This is a great image 😎", - source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), - size: .init(width: 50, height: 50), - aspectRatio: 1, - thumbnailSource: nil, + imageInfo: .mockImage, + thumbnailInfo: .mockThumbnail, blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW", contentType: .gif))) } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift index c0f4480ede..2763bb1313 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift @@ -14,15 +14,14 @@ struct StickerRoomTimelineView: View { var body: some View { TimelineStyler(timelineItem: timelineItem) { - LoadableImage(url: timelineItem.imageURL, + LoadableImage(mediaSource: timelineItem.imageInfo.source, mediaType: .timelineItem, blurhash: timelineItem.blurhash, - size: timelineItem.size, + size: timelineItem.imageInfo.size, mediaProvider: context.mediaProvider) { placeholder } - .timelineMediaFrame(height: timelineItem.size?.height, - aspectRatio: timelineItem.aspectRatio) + .timelineMediaFrame(imageInfo: timelineItem.imageInfo) .accessibilityElement(children: .ignore) .accessibilityLabel("\(L10n.commonSticker), \(timelineItem.body)") } @@ -51,7 +50,7 @@ struct StickerRoomTimelineView_Previews: PreviewProvider, TestablePreview { isEditable: false, canBeRepliedTo: true, sender: .init(id: "Bob"), - imageURL: URL.picturesDirectory)) + imageInfo: .mockImage)) StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .randomEvent, body: "Some other image", @@ -60,7 +59,7 @@ struct StickerRoomTimelineView_Previews: PreviewProvider, TestablePreview { isEditable: false, canBeRepliedTo: true, sender: .init(id: "Bob"), - imageURL: URL.picturesDirectory)) + imageInfo: .mockImage)) StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .randomEvent, body: "Blurhashed image", @@ -69,8 +68,7 @@ struct StickerRoomTimelineView_Previews: PreviewProvider, TestablePreview { isEditable: false, canBeRepliedTo: true, sender: .init(id: "Bob"), - imageURL: URL.picturesDirectory, - aspectRatio: 0.7, + imageInfo: .mockImage, blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW")) } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift index ea072fda76..d64d93c284 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift @@ -18,8 +18,7 @@ struct VideoRoomTimelineView: View { TimelineStyler(timelineItem: timelineItem) { VStack(alignment: .leading, spacing: 4) { thumbnail - .timelineMediaFrame(height: timelineItem.content.thumbnailSize?.height, - aspectRatio: timelineItem.content.aspectRatio) + .timelineMediaFrame(imageInfo: timelineItem.content.thumbnailInfo) .accessibilityElement(children: .ignore) .accessibilityLabel(L10n.commonVideo) // This clip shape is distinct from the one in the styler as that one @@ -41,11 +40,11 @@ struct VideoRoomTimelineView: View { @ViewBuilder var thumbnail: some View { - if let thumbnailSource = timelineItem.content.thumbnailSource { + if let thumbnailSource = timelineItem.content.thumbnailInfo?.source { LoadableImage(mediaSource: thumbnailSource, mediaType: .timelineItem, blurhash: timelineItem.content.blurhash, - size: timelineItem.content.thumbnailSize, + size: timelineItem.content.thumbnailInfo?.size, mediaProvider: context.mediaProvider) { imageView in imageView .overlay { playIcon } @@ -89,9 +88,8 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { isThreaded: false, sender: .init(id: "Bob"), content: .init(filename: "video.mp4", - duration: 21, - source: nil, - thumbnailSource: nil))) + videoInfo: .mockVideo, + thumbnailInfo: nil))) VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .randomEvent, timestamp: "Now", @@ -101,9 +99,8 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { isThreaded: false, sender: .init(id: "Bob"), content: .init(filename: "other.mp4", - duration: 22, - source: nil, - thumbnailSource: nil))) + videoInfo: .mockVideo, + thumbnailInfo: nil))) VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .randomEvent, timestamp: "Now", @@ -113,10 +110,8 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { isThreaded: false, sender: .init(id: "Bob"), content: .init(filename: "Blurhashed.mp4", - duration: 23, - source: nil, - aspectRatio: 0.7, - thumbnailSource: nil, + videoInfo: .mockVideo, + thumbnailInfo: nil, blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW"))) VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .randomEvent, @@ -128,9 +123,8 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { sender: .init(id: "Bob"), content: .init(filename: "video.mp4", caption: "This is a caption", - duration: 21, - source: nil, - thumbnailSource: nil))) + videoInfo: .mockVideo, + thumbnailInfo: nil))) } } } diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index 79b9eace51..da67a49fcc 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -258,11 +258,8 @@ enum RoomTimelineItemFixtures { isThreaded: false, sender: .init(id: ""), content: .init(filename: "video.mp4", - duration: 100, - source: .init(url: .picturesDirectory, mimeType: nil), - size: .init(width: 1920, height: 1080), - aspectRatio: 1.78, - thumbnailSource: .init(url: .picturesDirectory, mimeType: nil), + videoInfo: .mockVideo, + thumbnailInfo: .mockThumbnail, blurhash: "KtI~70X5V?yss9oyrYs:t6")), ImageRoomTimelineItem(id: .randomEvent, timestamp: "10:47 am", @@ -272,10 +269,8 @@ enum RoomTimelineItemFixtures { isThreaded: false, sender: .init(id: ""), content: .init(filename: "image.jpg", - source: .init(url: .picturesDirectory, mimeType: nil), - size: .init(width: 5120, height: 3412), - aspectRatio: 1.5, - thumbnailSource: nil, + imageInfo: .mockImage, + thumbnailInfo: nil, blurhash: "KpE4oyayR5|GbHb];3j@of")) ] } diff --git a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift index 93e8f94d95..11177b93bf 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift @@ -215,11 +215,111 @@ struct SendHandleProxy: Hashable { } } -extension Receipt { - var dateTimestamp: Date? { - guard let timestamp else { +struct VideoInfoProxy: Hashable { + let source: MediaSourceProxy + private(set) var duration: TimeInterval + private(set) var size: CGSize? + private(set) var aspectRatio: CGFloat? + private(set) var mimeType: String? + + init(source: MediaSource, duration: TimeInterval, width: UInt64?, height: UInt64?, mimeType: String?) { + self.source = MediaSourceProxy(source: source, mimeType: mimeType) + self.duration = duration + + let mediaInfo = MediaInfoProxy(width: width, height: height, mimeType: mimeType) + size = mediaInfo.size + aspectRatio = mediaInfo.aspectRatio + self.mimeType = mediaInfo.mimeType + } + + // MARK: - Mocks + + private init(source: MediaSourceProxy, duration: TimeInterval, size: CGSize?, aspectRatio: CGFloat?, mimeType: String?) { + self.source = source + self.duration = duration + self.size = size + self.aspectRatio = aspectRatio + self.mimeType = mimeType + } + + static var mockVideo: VideoInfoProxy { + .init(source: .init(url: .picturesDirectory, mimeType: nil), + duration: 100, + size: .init(width: 1920, height: 1080), + aspectRatio: 1.78, + mimeType: nil) + } +} + +struct ImageInfoProxy: Hashable { + let source: MediaSourceProxy + private(set) var size: CGSize? + private(set) var aspectRatio: CGFloat? + private(set) var mimeType: String? + + init?(source: MediaSource?, width: UInt64?, height: UInt64?, mimeType: String?) { + guard let source else { return nil } - return Date(timeIntervalSince1970: TimeInterval(timestamp / 1000)) + + self.init(source: .init(source: source, mimeType: mimeType), width: width, height: height, mimeType: mimeType) + } + + init(source: MediaSource, width: UInt64?, height: UInt64?, mimeType: String?) { + self.init(source: .init(source: source, mimeType: mimeType), width: width, height: height, mimeType: mimeType) + } + + init(url: URL, width: UInt64?, height: UInt64?, mimeType: String?) { + self.init(source: .init(url: url, mimeType: mimeType), width: width, height: height, mimeType: mimeType) + } + + init(source: MediaSourceProxy, width: UInt64?, height: UInt64?, mimeType: String?) { + self.source = source + + let mediaInfo = MediaInfoProxy(width: width, height: height, mimeType: mimeType) + size = mediaInfo.size + aspectRatio = mediaInfo.aspectRatio + self.mimeType = mediaInfo.mimeType + } + + // MARK: - Mocks + + private init(source: MediaSourceProxy, size: CGSize?, aspectRatio: CGFloat?, mimeType: String?) { + self.source = source + self.size = size + self.aspectRatio = aspectRatio + self.mimeType = mimeType + } + + static var mockImage: ImageInfoProxy { + .init(source: .init(url: .picturesDirectory, mimeType: "image/png"), + size: .init(width: 100, height: 100), + aspectRatio: 1, + mimeType: "image/png") + } + + static var mockThumbnail: ImageInfoProxy { + .init(source: .init(url: .picturesDirectory, mimeType: nil), + size: nil, + aspectRatio: nil, + mimeType: nil) + } +} + +struct MediaInfoProxy: Hashable { + private(set) var size: CGSize? + private(set) var mimeType: String? + private(set) var aspectRatio: CGFloat? + + init(width: UInt64?, height: UInt64?, mimeType: String?) { + if let width, let height { + size = .init(width: CGFloat(width), height: CGFloat(height)) + + if width > 0, height > 0 { + aspectRatio = CGFloat(width) / CGFloat(height) + } + } + + self.mimeType = mimeType } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItemContent.swift index 4bf326422e..f4c601d054 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItemContent.swift @@ -14,13 +14,9 @@ struct ImageRoomTimelineItemContent: Hashable { var formattedCaption: AttributedString? /// The original textual representation of the formatted caption directly from the event (usually HTML code) var formattedCaptionHTMLString: String? - let source: MediaSourceProxy - var size: CGSize? - var aspectRatio: CGFloat? - let thumbnailSource: MediaSourceProxy? - var thumbnailSize: CGSize? - var thumbnailAspectRatio: CGFloat? + let imageInfo: ImageInfoProxy + let thumbnailInfo: ImageInfoProxy? var blurhash: String? var contentType: UTType? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift index ff9ea5aa85..ea50f20781 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift @@ -14,15 +14,9 @@ struct VideoRoomTimelineItemContent: Hashable { var formattedCaption: AttributedString? /// The original textual representation of the formatted caption directly from the event (usually HTML code) var formattedCaptionHTMLString: String? - let duration: TimeInterval - let source: MediaSourceProxy? - var size: CGSize? - var aspectRatio: CGFloat? - - let thumbnailSource: MediaSourceProxy? - var thumbnailSize: CGSize? - var thumbnailAspectRatio: CGFloat? + let videoInfo: VideoInfoProxy + let thumbnailInfo: ImageInfoProxy? var blurhash: String? var contentType: UTType? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/StickerRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/StickerRoomTimelineItem.swift index be7613d56e..466ba2a0f4 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/StickerRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/StickerRoomTimelineItem.swift @@ -17,10 +17,8 @@ struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable { let sender: TimelineItemSender - let imageURL: URL + let imageInfo: ImageInfoProxy - var size: CGSize? - var aspectRatio: CGFloat? var blurhash: String? var properties = RoomTimelineItemProperties() diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 13be1ddd38..4f0401a4b6 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -118,20 +118,10 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { private func buildStickerTimelineItem(_ eventItemProxy: EventTimelineItemProxy, _ body: String, - _ imageInfo: ImageInfo, + _ info: MatrixRustSDK.ImageInfo, _ imageURL: URL, _ isOutgoing: Bool) -> RoomTimelineItemProtocol { - var imageSize: CGSize? - var imageAspectRatio: CGFloat? - - if let height = imageInfo.height, - let width = imageInfo.width { - imageSize = .init(width: CGFloat(width), height: CGFloat(height)) - - if width > 0, height > 0 { - imageAspectRatio = CGFloat(width) / CGFloat(height) - } - } + let imageInfo = ImageInfoProxy(url: imageURL, width: info.width, height: info.height, mimeType: info.mimetype) return StickerRoomTimelineItem(id: eventItemProxy.id, body: body, @@ -140,10 +130,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { isEditable: eventItemProxy.isEditable, canBeRepliedTo: eventItemProxy.canBeRepliedTo, sender: eventItemProxy.sender, - imageURL: imageURL, - size: imageSize, - aspectRatio: imageAspectRatio, - blurhash: imageInfo.blurhash, + imageInfo: imageInfo, + blurhash: info.blurhash, properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus, orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts), @@ -522,91 +510,47 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { let htmlCaption = messageContent.formattedCaption?.format == .html ? messageContent.formattedCaption?.body : nil let formattedCaption = htmlCaption != nil ? attributedStringBuilder.fromHTML(htmlCaption) : attributedStringBuilder.fromPlain(messageContent.caption) - let thumbnailSource = messageContent.info?.thumbnailSource.map { MediaSourceProxy(source: $0, mimeType: messageContent.info?.thumbnailInfo?.mimetype) } - - var thumbnailSize: CGSize? - var thumbnailAspectRatio: CGFloat? - - if let thumbnailInfo = messageContent.info?.thumbnailInfo, - let height = thumbnailInfo.height, - let width = thumbnailInfo.width { - thumbnailSize = .init(width: CGFloat(width), height: CGFloat(height)) - - if width > 0, height > 0 { - thumbnailAspectRatio = CGFloat(width) / CGFloat(height) - } - } - - var imageSize: CGSize? - var imageAspectRatio: CGFloat? + let thumbnailInfo = ImageInfoProxy(source: messageContent.info?.thumbnailSource, + width: messageContent.info?.thumbnailInfo?.width, + height: messageContent.info?.thumbnailInfo?.height, + mimeType: messageContent.info?.thumbnailInfo?.mimetype) - if let imageInfo = messageContent.info, - let height = imageInfo.height, - let width = imageInfo.width { - imageSize = .init(width: CGFloat(width), height: CGFloat(height)) - - if width > 0, height > 0 { - imageAspectRatio = CGFloat(width) / CGFloat(height) - } - } + let imageInfo = ImageInfoProxy(source: messageContent.source, + width: messageContent.info?.width, + height: messageContent.info?.height, + mimeType: messageContent.info?.mimetype) return .init(filename: messageContent.filename, caption: messageContent.caption, formattedCaption: formattedCaption, formattedCaptionHTMLString: htmlCaption, - source: MediaSourceProxy(source: messageContent.source, mimeType: messageContent.info?.mimetype), - size: imageSize, - aspectRatio: imageAspectRatio, - thumbnailSource: thumbnailSource, - thumbnailSize: thumbnailSize, - thumbnailAspectRatio: thumbnailAspectRatio, + imageInfo: imageInfo, + thumbnailInfo: thumbnailInfo, blurhash: messageContent.info?.blurhash, contentType: UTType(mimeType: messageContent.info?.mimetype, fallbackFilename: messageContent.filename)) } - + private func buildVideoTimelineItemContent(_ messageContent: VideoMessageContent) -> VideoRoomTimelineItemContent { let htmlCaption = messageContent.formattedCaption?.format == .html ? messageContent.formattedCaption?.body : nil let formattedCaption = htmlCaption != nil ? attributedStringBuilder.fromHTML(htmlCaption) : attributedStringBuilder.fromPlain(messageContent.caption) - let thumbnailSource = messageContent.info?.thumbnailSource.map { MediaSourceProxy(source: $0, mimeType: messageContent.info?.thumbnailInfo?.mimetype) } - - var thumbnailSize: CGSize? - var thumbnailAspectRatio: CGFloat? + let thumbnailInfo = ImageInfoProxy(source: messageContent.info?.thumbnailSource, + width: messageContent.info?.thumbnailInfo?.width, + height: messageContent.info?.thumbnailInfo?.height, + mimeType: messageContent.info?.thumbnailInfo?.mimetype) - if let thumbnailInfo = messageContent.info?.thumbnailInfo, - let height = thumbnailInfo.height, - let width = thumbnailInfo.width { - thumbnailSize = .init(width: CGFloat(width), height: CGFloat(height)) - - if width > 0, height > 0 { - thumbnailAspectRatio = CGFloat(width) / CGFloat(height) - } - } - - var videoSize: CGSize? - var videoAspectRatio: CGFloat? - - if let videoInfo = messageContent.info, - let height = videoInfo.height, - let width = videoInfo.width { - videoSize = .init(width: CGFloat(width), height: CGFloat(height)) - - if width > 0, height > 0 { - videoAspectRatio = CGFloat(width) / CGFloat(height) - } - } + let videoInfo = VideoInfoProxy(source: messageContent.source, + duration: messageContent.info?.duration ?? 0, + width: messageContent.info?.width, + height: messageContent.info?.height, + mimeType: messageContent.info?.mimetype) return .init(filename: messageContent.filename, caption: messageContent.caption, formattedCaption: formattedCaption, formattedCaptionHTMLString: htmlCaption, - duration: messageContent.info?.duration ?? 0, - source: MediaSourceProxy(source: messageContent.source, mimeType: messageContent.info?.mimetype), - size: videoSize, - aspectRatio: videoAspectRatio, - thumbnailSource: thumbnailSource, - thumbnailSize: thumbnailSize, - thumbnailAspectRatio: thumbnailAspectRatio, + videoInfo: videoInfo, + thumbnailInfo: thumbnailInfo, blurhash: messageContent.info?.blurhash, contentType: UTType(mimeType: messageContent.info?.mimetype, fallbackFilename: messageContent.filename)) } @@ -821,3 +765,12 @@ private extension RepliedToEventDetails { } } } + +private extension Receipt { + var dateTimestamp: Date? { + guard let timestamp else { + return nil + } + return Date(timeIntervalSince1970: TimeInterval(timestamp / 1000)) + } +} 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 cd99b8548f..92fed002bb 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:e96e572bd736c19bb9f3bd37216bd464276c9a1ebbd715fdae27973f9b6f36cc -size 333847 +oid sha256:82ce96aa44eb950fb131e85d6ceeab485a5985fb48c30b4d954c65602e77686a +size 281497 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 cd99b8548f..92fed002bb 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:e96e572bd736c19bb9f3bd37216bd464276c9a1ebbd715fdae27973f9b6f36cc -size 333847 +oid sha256:82ce96aa44eb950fb131e85d6ceeab485a5985fb48c30b4d954c65602e77686a +size 281497 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 2180dd9b0f..43321c49a7 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:13f6dfd3e07222ae3757076e453cf99695424db88c8cc85fede448ea64e70519 -size 276862 +oid sha256:d08539366199fb29c4f2b01b6b399426b928a0eaae94306441d0a6cf34be4a13 +size 225250 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 2180dd9b0f..43321c49a7 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:13f6dfd3e07222ae3757076e453cf99695424db88c8cc85fede448ea64e70519 -size 276862 +oid sha256:d08539366199fb29c4f2b01b6b399426b928a0eaae94306441d0a6cf34be4a13 +size 225250 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPad-en-GB.1.png index a40034429b..dea3683a8d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd110afc7d00be27146e58474b810415af9dc7d5ed6faf1536419f01f41f9a3a -size 404161 +oid sha256:26f2e07fa87d8ae6a910f30e992cf45e528715093663f40739e085c1349a6b31 +size 218285 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPad-pseudo.1.png index a40034429b..dea3683a8d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd110afc7d00be27146e58474b810415af9dc7d5ed6faf1536419f01f41f9a3a -size 404161 +oid sha256:26f2e07fa87d8ae6a910f30e992cf45e528715093663f40739e085c1349a6b31 +size 218285 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPhone-16-en-GB.1.png index 3ab5d2b0bd..7ab6b4378a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef76e5d6f293ae298df0e70744ffed330e1fcb4a1e1157fbd7578e59bbe52fd4 -size 336667 +oid sha256:23b75089dd40fc4c0c8cbaeb97f9659b19c22621133442e09ec7fc962cbae091 +size 167471 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPhone-16-pseudo.1.png index 3ab5d2b0bd..7ab6b4378a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_stickerRoomTimelineView-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef76e5d6f293ae298df0e70744ffed330e1fcb4a1e1157fbd7578e59bbe52fd4 -size 336667 +oid sha256:23b75089dd40fc4c0c8cbaeb97f9659b19c22621133442e09ec7fc962cbae091 +size 167471 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 18df7f1a0b..c11fbddd93 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:f0f9e472dd0a7675c25bc5856aca8f3e8ed94b157aa2dea3683e8a7cf17b588c -size 94892 +oid sha256:ba0a7cbad0cae44f11230bfd240445eeca207ddf22d6e3b5882e69d448cf2a9b +size 95167 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 18df7f1a0b..c11fbddd93 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:f0f9e472dd0a7675c25bc5856aca8f3e8ed94b157aa2dea3683e8a7cf17b588c -size 94892 +oid sha256:ba0a7cbad0cae44f11230bfd240445eeca207ddf22d6e3b5882e69d448cf2a9b +size 95167 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 d3bd5b56d3..4bb5d6f1f0 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:8e4e25d87b4335854f8cd67072ef0e8e89a50a9a56dbc10b6cf01ee725731767 -size 52197 +oid sha256:d74e45a7d9ceb6b72d3c64aa300fbc122f18aef4c3b6cef630850c3cd242a8c4 +size 52016 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 d3bd5b56d3..4bb5d6f1f0 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:8e4e25d87b4335854f8cd67072ef0e8e89a50a9a56dbc10b6cf01ee725731767 -size 52197 +oid sha256:d74e45a7d9ceb6b72d3c64aa300fbc122f18aef4c3b6cef630850c3cd242a8c4 +size 52016 diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index 9e2c34e7de..26595f3613 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -150,8 +150,8 @@ class LoggingTests: XCTestCase { sender: .init(id: "sender"), content: .init(filename: "ImageString", caption: "ImageString", - source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/gif"), - thumbnailSource: nil)) + imageInfo: .mockImage, + thumbnailInfo: nil)) let videoMessage = VideoRoomTimelineItem(id: .randomEvent, timestamp: "", isOutgoing: false, @@ -161,9 +161,8 @@ class LoggingTests: XCTestCase { sender: .init(id: "sender"), content: .init(filename: "VideoString", caption: "VideoString", - duration: 0, - source: nil, - thumbnailSource: nil)) + videoInfo: .mockVideo, + thumbnailInfo: nil)) let fileMessage = FileRoomTimelineItem(id: .randomEvent, timestamp: "", isOutgoing: false,