mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Added blurhash and correct sizes to timeline images and animations throughout the app.
This commit is contained in:
@@ -49,6 +49,7 @@
|
|||||||
18ADC7FA27EB02D900A8C953 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18ADC7F927EB02D900A8C953 /* AttributedStringBuilder.swift */; };
|
18ADC7FA27EB02D900A8C953 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18ADC7F927EB02D900A8C953 /* AttributedStringBuilder.swift */; };
|
||||||
18ADC7FE27EB033400A8C953 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 18ADC7FD27EB033400A8C953 /* DTCoreText */; };
|
18ADC7FE27EB033400A8C953 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 18ADC7FD27EB033400A8C953 /* DTCoreText */; };
|
||||||
18ADC80627EB1ED100A8C953 /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 18ADC80427EB1ED100A8C953 /* UIFont+AttributedStringBuilder.m */; };
|
18ADC80627EB1ED100A8C953 /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 18ADC80427EB1ED100A8C953 /* UIFont+AttributedStringBuilder.m */; };
|
||||||
|
18BB170A27FC20D20074CBD6 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18BB170927FC20D20074CBD6 /* BlurHashDecode.swift */; };
|
||||||
18C5744C27E1D84000D70937 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */; };
|
18C5744C27E1D84000D70937 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */; };
|
||||||
18C5744D27E1D84000D70937 /* RoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5744A27E1D84000D70937 /* RoomProxy.swift */; };
|
18C5744D27E1D84000D70937 /* RoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5744A27E1D84000D70937 /* RoomProxy.swift */; };
|
||||||
18C5744E27E1D84000D70937 /* MockRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5744B27E1D84000D70937 /* MockRoomProxy.swift */; };
|
18C5744E27E1D84000D70937 /* MockRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5744B27E1D84000D70937 /* MockRoomProxy.swift */; };
|
||||||
@@ -189,6 +190,7 @@
|
|||||||
18ADC80527EB1ED100A8C953 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
|
18ADC80527EB1ED100A8C953 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
|
||||||
18ADC80727EB1EE200A8C953 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = "<group>"; };
|
18ADC80727EB1EE200A8C953 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
18BB170627FB74B30074CBD6 /* matrix-rust-components-swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "matrix-rust-components-swift"; path = "../matrix-rust-components-swift"; sourceTree = "<group>"; };
|
18BB170627FB74B30074CBD6 /* matrix-rust-components-swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "matrix-rust-components-swift"; path = "../matrix-rust-components-swift"; sourceTree = "<group>"; };
|
||||||
|
18BB170927FC20D20074CBD6 /* BlurHashDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||||
18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
18C5744A27E1D84000D70937 /* RoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
18C5744A27E1D84000D70937 /* RoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
|
||||||
18C5744B27E1D84000D70937 /* MockRoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = "<group>"; };
|
18C5744B27E1D84000D70937 /* MockRoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = "<group>"; };
|
||||||
@@ -537,6 +539,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
184858EA27FAF6D300B8AC7B /* Benchmark.swift */,
|
184858EA27FAF6D300B8AC7B /* Benchmark.swift */,
|
||||||
|
18BB170927FC20D20074CBD6 /* BlurHashDecode.swift */,
|
||||||
18920C1827F3222E00A717B5 /* Colors.swift */,
|
18920C1827F3222E00A717B5 /* Colors.swift */,
|
||||||
18F2BA9A27D25B4000DD1988 /* Coordinator.swift */,
|
18F2BA9A27D25B4000DD1988 /* Coordinator.swift */,
|
||||||
18DDB72D27EF936D000F1ABF /* MatrixEntitityRegex.swift */,
|
18DDB72D27EF936D000F1ABF /* MatrixEntitityRegex.swift */,
|
||||||
@@ -1003,6 +1006,7 @@
|
|||||||
181A317B27F70AEB00B8305B /* EventBrief.swift in Sources */,
|
181A317B27F70AEB00B8305B /* EventBrief.swift in Sources */,
|
||||||
181A317927F703E300B8305B /* MockRoomSummary.swift in Sources */,
|
181A317927F703E300B8305B /* MockRoomSummary.swift in Sources */,
|
||||||
1850256F27B6A135002E6B18 /* AppDelegate.swift in Sources */,
|
1850256F27B6A135002E6B18 /* AppDelegate.swift in Sources */,
|
||||||
|
18BB170A27FC20D20074CBD6 /* BlurHashDecode.swift in Sources */,
|
||||||
18F2BAE627D25B4000DD1988 /* NavigationRouterType.swift in Sources */,
|
18F2BAE627D25B4000DD1988 /* NavigationRouterType.swift in Sources */,
|
||||||
184858EB27FAF6D300B8AC7B /* Benchmark.swift in Sources */,
|
184858EB27FAF6D300B8AC7B /* Benchmark.swift in Sources */,
|
||||||
18F2BAE927D25B4000DD1988 /* ActivityPresentable.swift in Sources */,
|
18F2BAE927D25B4000DD1988 /* ActivityPresentable.swift in Sources */,
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
// swiftlint:disable all
|
||||||
|
|
||||||
|
extension UIImage {
|
||||||
|
public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
|
||||||
|
guard blurHash.count >= 6 else { return nil }
|
||||||
|
|
||||||
|
let sizeFlag = String(blurHash[0]).decode83()
|
||||||
|
let numY = (sizeFlag / 9) + 1
|
||||||
|
let numX = (sizeFlag % 9) + 1
|
||||||
|
|
||||||
|
let quantisedMaximumValue = String(blurHash[1]).decode83()
|
||||||
|
let maximumValue = Float(quantisedMaximumValue + 1) / 166
|
||||||
|
|
||||||
|
guard blurHash.count == 4 + 2 * numX * numY else { return nil }
|
||||||
|
|
||||||
|
let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in
|
||||||
|
if i == 0 {
|
||||||
|
let value = String(blurHash[2 ..< 6]).decode83()
|
||||||
|
return decodeDC(value)
|
||||||
|
} else {
|
||||||
|
let value = String(blurHash[4 + i * 2 ..< 4 + i * 2 + 2]).decode83()
|
||||||
|
return decodeAC(value, maximumValue: maximumValue * punch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = Int(size.width)
|
||||||
|
let height = Int(size.height)
|
||||||
|
let bytesPerRow = width * 3
|
||||||
|
guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil }
|
||||||
|
CFDataSetLength(data, bytesPerRow * height)
|
||||||
|
guard let pixels = CFDataGetMutableBytePtr(data) else { return nil }
|
||||||
|
|
||||||
|
for y in 0 ..< height {
|
||||||
|
for x in 0 ..< width {
|
||||||
|
var r: Float = 0
|
||||||
|
var g: Float = 0
|
||||||
|
var b: Float = 0
|
||||||
|
|
||||||
|
for j in 0 ..< numY {
|
||||||
|
for i in 0 ..< numX {
|
||||||
|
let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height))
|
||||||
|
let colour = colours[i + j * numX]
|
||||||
|
r += colour.0 * basis
|
||||||
|
g += colour.1 * basis
|
||||||
|
b += colour.2 * basis
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let intR = UInt8(linearTosRGB(r))
|
||||||
|
let intG = UInt8(linearTosRGB(g))
|
||||||
|
let intB = UInt8(linearTosRGB(b))
|
||||||
|
|
||||||
|
pixels[3 * x + 0 + y * bytesPerRow] = intR
|
||||||
|
pixels[3 * x + 1 + y * bytesPerRow] = intG
|
||||||
|
pixels[3 * x + 2 + y * bytesPerRow] = intB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
|
||||||
|
|
||||||
|
guard let provider = CGDataProvider(data: data) else { return nil }
|
||||||
|
guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow,
|
||||||
|
space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil }
|
||||||
|
|
||||||
|
self.init(cgImage: cgImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func decodeDC(_ value: Int) -> (Float, Float, Float) {
|
||||||
|
let intR = value >> 16
|
||||||
|
let intG = (value >> 8) & 255
|
||||||
|
let intB = value & 255
|
||||||
|
return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) {
|
||||||
|
let quantR = value / (19 * 19)
|
||||||
|
let quantG = (value / 19) % 19
|
||||||
|
let quantB = value % 19
|
||||||
|
|
||||||
|
let rgb = (
|
||||||
|
signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
|
||||||
|
signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
|
||||||
|
signPow((Float(quantB) - 9) / 9, 2) * maximumValue
|
||||||
|
)
|
||||||
|
|
||||||
|
return rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
private func signPow(_ value: Float, _ exp: Float) -> Float {
|
||||||
|
return copysign(pow(abs(value), exp), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func linearTosRGB(_ value: Float) -> Int {
|
||||||
|
let v = max(0, min(1, value))
|
||||||
|
if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) }
|
||||||
|
else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float {
|
||||||
|
let v = Float(Int64(value)) / 255
|
||||||
|
if v <= 0.04045 { return v / 12.92 }
|
||||||
|
else { return pow((v + 0.055) / 1.055, 2.4) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private let encodeCharacters: [String] = {
|
||||||
|
return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) }
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let decodeCharacters: [String: Int] = {
|
||||||
|
var dict: [String: Int] = [:]
|
||||||
|
for (index, character) in encodeCharacters.enumerated() {
|
||||||
|
dict[character] = index
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}()
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
func decode83() -> Int {
|
||||||
|
var value: Int = 0
|
||||||
|
for character in self {
|
||||||
|
if let digit = decodeCharacters[String(character)] {
|
||||||
|
value = value * 83 + digit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
subscript (offset: Int) -> Character {
|
||||||
|
return self[index(startIndex, offsetBy: offset)]
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript (bounds: CountableClosedRange<Int>) -> Substring {
|
||||||
|
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||||
|
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||||
|
return self[start...end]
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript (bounds: CountableRange<Int>) -> Substring {
|
||||||
|
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||||
|
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||||
|
return self[start..<end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:enable all
|
||||||
@@ -52,7 +52,7 @@ struct HomeScreenViewState: BindableState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HomeScreenRoom: Identifiable {
|
struct HomeScreenRoom: Identifiable, Equatable {
|
||||||
let id: String
|
let id: String
|
||||||
|
|
||||||
var displayName: String?
|
var displayName: String?
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ struct HomeScreen: View {
|
|||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
}
|
}
|
||||||
|
.animation(.easeInOut, value: context.viewState.userAvatar)
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button("Logout") {
|
Button("Logout") {
|
||||||
@@ -135,6 +136,7 @@ struct RoomCell: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.animation(.easeInOut, value: room)
|
||||||
.frame(minHeight: 60.0)
|
.frame(minHeight: 60.0)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
context.send(viewAction: .loadRoomData(roomIdentifier: room.id))
|
context.send(viewAction: .loadRoomData(roomIdentifier: room.id))
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ struct EventBasedTimelineView: View {
|
|||||||
}
|
}
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.frame(width: 24.0, height: 24.0)
|
.frame(width: 24.0, height: 24.0)
|
||||||
|
.animation(.easeInOut, value: timelineItem.senderAvatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var firstLetter: String {
|
private var firstLetter: String {
|
||||||
|
|||||||
@@ -13,21 +13,39 @@ struct ImageRoomTimelineView: View {
|
|||||||
let timelineItem: ImageRoomTimelineItem
|
let timelineItem: ImageRoomTimelineItem
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let image = timelineItem.image {
|
if timelineItem.image != nil || timelineItem.blurhash != nil { // Fixes view heights after loading finishes
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
EventBasedTimelineView(timelineItem: timelineItem)
|
EventBasedTimelineView(timelineItem: timelineItem)
|
||||||
Text(timelineItem.text)
|
Text(timelineItem.text)
|
||||||
Image(uiImage: image)
|
if let image = timelineItem.image {
|
||||||
.resizable()
|
if let aspectRatio = timelineItem.aspectRatio {
|
||||||
.scaledToFit()
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(aspectRatio, contentMode: .fit)
|
||||||
|
} else {
|
||||||
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
}
|
||||||
|
} else if let blurhash = timelineItem.blurhash,
|
||||||
|
// Build a small blurhash image so that it's fast
|
||||||
|
let image = UIImage(blurHash: blurhash, size: .init(width: 10.0, height: 10.0)) {
|
||||||
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(timelineItem.aspectRatio, contentMode: .fit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.animation(.easeInOut, value: timelineItem.image)
|
||||||
|
.frame(maxHeight: 1000.0)
|
||||||
} else {
|
} else {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .leading) {
|
||||||
|
EventBasedTimelineView(timelineItem: timelineItem)
|
||||||
|
Text(timelineItem.text)
|
||||||
HStack {
|
HStack {
|
||||||
Text(timelineItem.text)
|
Spacer()
|
||||||
|
ProgressView("Loading")
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
ProgressView("Loading")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +68,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
|
|||||||
url: nil,
|
url: nil,
|
||||||
image: UIImage(systemName: "photo"))
|
image: UIImage(systemName: "photo"))
|
||||||
ImageRoomTimelineView(timelineItem: timelineItem)
|
ImageRoomTimelineView(timelineItem: timelineItem)
|
||||||
|
|
||||||
let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString,
|
let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString,
|
||||||
text: "Some other image",
|
text: "Some other image",
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
@@ -59,6 +77,17 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
|
|||||||
url: nil,
|
url: nil,
|
||||||
image: nil)
|
image: nil)
|
||||||
ImageRoomTimelineView(timelineItem: timelineItem)
|
ImageRoomTimelineView(timelineItem: timelineItem)
|
||||||
|
|
||||||
|
let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString,
|
||||||
|
text: "Blurhashed image",
|
||||||
|
timestamp: "Now",
|
||||||
|
shouldShowSenderDetails: false,
|
||||||
|
senderId: "Bob",
|
||||||
|
url: nil,
|
||||||
|
image: nil,
|
||||||
|
aspectRatio: 0.7,
|
||||||
|
blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW")
|
||||||
|
ImageRoomTimelineView(timelineItem: timelineItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Copyright © 2022 Element. All rights reserved.
|
// Copyright © 2022 Element. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import UIKit
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
struct ImageRoomMessage: RoomMessageProtocol {
|
struct ImageRoomMessage: RoomMessageProtocol {
|
||||||
@@ -35,4 +35,24 @@ struct ImageRoomMessage: RoomMessageProtocol {
|
|||||||
var url: String? {
|
var url: String? {
|
||||||
message.url()
|
message.url()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var width: CGFloat? {
|
||||||
|
guard let width = message.width() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return CGFloat(width)
|
||||||
|
}
|
||||||
|
|
||||||
|
var height: CGFloat? {
|
||||||
|
guard let height = message.height() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return CGFloat(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var blurhash: String? {
|
||||||
|
message.blurhash()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,4 +21,9 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
|
|||||||
|
|
||||||
let url: String?
|
let url: String?
|
||||||
var image: UIImage?
|
var image: UIImage?
|
||||||
|
|
||||||
|
var width: CGFloat?
|
||||||
|
var height: CGFloat?
|
||||||
|
var aspectRatio: CGFloat?
|
||||||
|
var blurhash: String?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,13 @@ struct RoomTimelineItemFactory {
|
|||||||
_ showSenderDetails: Bool,
|
_ showSenderDetails: Bool,
|
||||||
_ displayName: String?,
|
_ displayName: String?,
|
||||||
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
|
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
|
||||||
|
|
||||||
|
var aspectRatio: CGFloat?
|
||||||
|
if let width = message.width,
|
||||||
|
let height = message.height {
|
||||||
|
aspectRatio = width / height
|
||||||
|
}
|
||||||
|
|
||||||
return ImageRoomTimelineItem(id: message.id,
|
return ImageRoomTimelineItem(id: message.id,
|
||||||
text: message.body,
|
text: message.body,
|
||||||
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
|
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
|
||||||
@@ -71,7 +78,11 @@ struct RoomTimelineItemFactory {
|
|||||||
senderDisplayName: displayName,
|
senderDisplayName: displayName,
|
||||||
senderAvatar: avatarImage,
|
senderAvatar: avatarImage,
|
||||||
url: message.url,
|
url: message.url,
|
||||||
image: mediaProvider.imageForURL(message.url))
|
image: mediaProvider.imageForURL(message.url),
|
||||||
|
width: message.width,
|
||||||
|
height: message.height,
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
blurhash: message.blurhash)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildNoticeTimelineItemFromMessage(_ message: NoticeRoomMessage,
|
private func buildNoticeTimelineItemFromMessage(_ message: NoticeRoomMessage,
|
||||||
|
|||||||
Reference in New Issue
Block a user