mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Group image and video metadata in specialised structs (#3518)
This commit is contained in:
@@ -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"))
|
||||
]
|
||||
}
|
||||
|
||||
@@ -215,11 +215,111 @@ struct SendHandleProxy: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Receipt {
|
||||
var dateTimestamp: Date? {
|
||||
guard let timestamp else {
|
||||
return nil
|
||||
}
|
||||
return Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -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?
|
||||
|
||||
+2
-8
@@ -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?
|
||||
|
||||
+1
-3
@@ -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()
|
||||
|
||||
@@ -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) }
|
||||
let thumbnailInfo = ImageInfoProxy(source: messageContent.info?.thumbnailSource,
|
||||
width: messageContent.info?.thumbnailInfo?.width,
|
||||
height: messageContent.info?.thumbnailInfo?.height,
|
||||
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?
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user