Introduced MemberDetailsProvider, caching user avatar urls and images. Various other tweaks and improvements.

This commit is contained in:
Stefan Ceriu
2022-03-18 13:40:25 +02:00
parent 120bcaf012
commit 2eae0f0573
22 changed files with 346 additions and 203 deletions
@@ -0,0 +1,13 @@
//
// DecorationTimelineItemProtocol.swift
// ElementX
//
// Created by Stefan Ceriu on 18/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
protocol DecorationTimelineItemProtocol: RoomTimelineItemProtocol {
}
@@ -0,0 +1,19 @@
//
// EventBasedTimelineItemProtocol.swift
// ElementX
//
// Created by Stefan Ceriu on 18/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import UIKit
protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol {
var text: String { get }
var timestamp: String { get }
var shouldShowSenderDetails: Bool { get }
var sender: String { get }
var senderAvatar: UIImage? { get set }
}
@@ -9,7 +9,7 @@
import Foundation
import UIKit
struct ImageRoomTimelineItem: BaseRoomTimelineItemProtocol, Identifiable, Equatable {
struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable {
let id: String
let text: String
let timestamp: String
@@ -8,7 +8,7 @@
import Foundation
struct SeparatorRoomTimelineItem: RoomTimelineItemProtocol, Identifiable, Equatable {
struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Equatable {
let id: String
let text: String
}
@@ -9,7 +9,7 @@
import Foundation
import UIKit
struct TextRoomTimelineItem: BaseRoomTimelineItemProtocol, Identifiable, Equatable {
struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable {
let id: String
let text: String
let timestamp: String
@@ -11,39 +11,36 @@ import UIKit
struct RoomTimelineItemFactory {
private let mediaProvider: MediaProviderProtocol
private let memberDetailsProvider: MemberDetailsProvider
init(mediaProvider: MediaProviderProtocol) {
init(mediaProvider: MediaProviderProtocol,
memberDetailsProvider: MemberDetailsProvider) {
self.mediaProvider = mediaProvider
self.memberDetailsProvider = memberDetailsProvider
}
func buildTimelineItemFor(_ roomMessage: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol {
let avatarURL = memberDetailsProvider.avatarURLForUserId(roomMessage.sender)
let avatarImage = mediaProvider.imageForURL(avatarURL)
switch roomMessage {
case let message as TextRoomMessage:
return TextRoomTimelineItem(id: message.id,
text: message.content,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
sender: message.sender)
sender: message.sender,
senderAvatar: avatarImage)
case let message as ImageRoomMessage:
var image: UIImage?
if let url = message.url {
if mediaProvider.hasImageCachedForURL(url) {
mediaProvider.loadImageFromURL(url, { result in
if case let .success(cachedImage) = result {
image = cachedImage
}
})
}
}
return ImageRoomTimelineItem(id: message.id,
text: message.content,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
sender: message.sender,
senderAvatar: avatarImage,
url: message.url,
image: image)
image: mediaProvider.imageForURL(message.url))
default:
fatalError("Unknown room message.")
}
@@ -12,12 +12,3 @@ import UIKit
protocol RoomTimelineItemProtocol {
var id: String { get }
}
protocol BaseRoomTimelineItemProtocol: RoomTimelineItemProtocol {
var text: String { get }
var timestamp: String { get }
var shouldShowSenderDetails: Bool { get }
var sender: String { get }
var senderAvatar: UIImage? { get set }
}
@@ -0,0 +1,44 @@
//
// EventBasedTimelineView.swift
// ElementX
//
// Created by Stefan Ceriu on 18/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import SwiftUI
struct EventBasedTimelineView: View {
let timelineItem: EventBasedTimelineItemProtocol
var body: some View {
if timelineItem.shouldShowSenderDetails {
HStack {
avatar
Text(timelineItem.sender)
.font(.footnote)
.bold()
Spacer()
Text(timelineItem.timestamp)
.font(.footnote)
}
Divider()
}
}
@ViewBuilder private var avatar: some View {
ZStack(alignment: .center) {
if let avatar = timelineItem.senderAvatar {
Image(uiImage: avatar)
.resizable()
.scaledToFill()
.overlay(Circle().stroke(Color(.sRGB, red: 0.05, green: 0.74, blue: 0.55, opacity: 1.0)))
} else {
PlaceholderAvatarImage(firstCharacter: String(timelineItem.sender.prefix(2).suffix(1)).uppercased())
}
}
.clipShape(Circle())
.frame(width: 44.0, height: 44.0)
}
}
@@ -14,11 +14,9 @@ struct ImageRoomTimelineView: View {
var body: some View {
if let image = timelineItem.image {
VStack {
HStack {
Text(timelineItem.text)
Spacer()
}
VStack(alignment: .leading) {
EventBasedTimelineView(timelineItem: timelineItem)
Text(timelineItem.text)
Image(uiImage: image)
.resizable()
.scaledToFit()
@@ -14,40 +14,13 @@ struct TextRoomTimelineView: View {
var body: some View {
VStack(alignment: .leading) {
if timelineItem.shouldShowSenderDetails {
HStack {
avatar
Text(timelineItem.sender)
.font(.footnote)
.bold()
Spacer()
Text(timelineItem.timestamp)
.font(.footnote)
}
Divider()
}
EventBasedTimelineView(timelineItem: timelineItem)
Text(timelineItem.text)
.fixedSize(horizontal: false, vertical: true)
}
.id(timelineItem.id)
}
@ViewBuilder var avatar: some View {
ZStack(alignment: .center) {
Circle()
.fill(Color(.sRGB, red: 0.05, green: 0.74, blue: 0.55, opacity: 1.0))
if let avatar = timelineItem.senderAvatar {
Image(uiImage: avatar)
.resizable()
.clipShape(Circle())
} else {
Text(timelineItem.sender.prefix(2).suffix(1))
.foregroundColor(.white)
.font(.title)
.bold()
}
}
.frame(width: 44.0, height: 44.0)
}
}
struct TextRoomTimelineView_Previews: PreviewProvider {