Caching memberDetailProviders, showing sender display names in home screen last message, cleaner homescreen design.

This commit is contained in:
Stefan Ceriu
2022-04-01 16:05:26 +03:00
parent 86d85f4d2c
commit 711857ac9c
15 changed files with 141 additions and 70 deletions
+4
View File
@@ -26,6 +26,7 @@
1850257127B6A135002E6B18 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1850256927B6A135002E6B18 /* LaunchScreen.storyboard */; }; 1850257127B6A135002E6B18 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1850256927B6A135002E6B18 /* LaunchScreen.storyboard */; };
1863A3FC27BA5A9100B52E4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A3FB27BA5A9100B52E4D /* KeychainAccess */; }; 1863A3FC27BA5A9100B52E4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A3FB27BA5A9100B52E4D /* KeychainAccess */; };
1863A40627BA6DFC00B52E4D /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A40527BA6DFC00B52E4D /* SwiftyBeaver */; }; 1863A40627BA6DFC00B52E4D /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A40527BA6DFC00B52E4D /* SwiftyBeaver */; };
186AD17D27F72E5200048C8E /* MemberDetailProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186AD17C27F72E5200048C8E /* MemberDetailProviderManager.swift */; };
18920C0E27F233FF00A717B5 /* NoticeRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C0C27F233FF00A717B5 /* NoticeRoomMessage.swift */; }; 18920C0E27F233FF00A717B5 /* NoticeRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C0C27F233FF00A717B5 /* NoticeRoomMessage.swift */; };
18920C0F27F233FF00A717B5 /* EmoteRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C0D27F233FF00A717B5 /* EmoteRoomMessage.swift */; }; 18920C0F27F233FF00A717B5 /* EmoteRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C0D27F233FF00A717B5 /* EmoteRoomMessage.swift */; };
18920C1227F2347600A717B5 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C1027F2347600A717B5 /* NoticeRoomTimelineItem.swift */; }; 18920C1227F2347600A717B5 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C1027F2347600A717B5 /* NoticeRoomTimelineItem.swift */; };
@@ -166,6 +167,7 @@
1850256727B6A135002E6B18 /* ElementX.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = "<group>"; }; 1850256727B6A135002E6B18 /* ElementX.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = "<group>"; };
1850256827B6A135002E6B18 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 1850256827B6A135002E6B18 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1850256A27B6A135002E6B18 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 1850256A27B6A135002E6B18 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
186AD17C27F72E5200048C8E /* MemberDetailProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailProviderManager.swift; sourceTree = "<group>"; };
18920C0C27F233FF00A717B5 /* NoticeRoomMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoticeRoomMessage.swift; sourceTree = "<group>"; }; 18920C0C27F233FF00A717B5 /* NoticeRoomMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoticeRoomMessage.swift; sourceTree = "<group>"; };
18920C0D27F233FF00A717B5 /* EmoteRoomMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmoteRoomMessage.swift; sourceTree = "<group>"; }; 18920C0D27F233FF00A717B5 /* EmoteRoomMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmoteRoomMessage.swift; sourceTree = "<group>"; };
18920C1027F2347600A717B5 /* NoticeRoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItem.swift; sourceTree = "<group>"; }; 18920C1027F2347600A717B5 /* NoticeRoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItem.swift; sourceTree = "<group>"; };
@@ -487,6 +489,7 @@
children = ( children = (
183E023327E4A73C00903BED /* MemberDetailsProviderProtocol.swift */, 183E023327E4A73C00903BED /* MemberDetailsProviderProtocol.swift */,
18DF7C5227E4754500291672 /* MemberDetailsProvider.swift */, 18DF7C5227E4754500291672 /* MemberDetailsProvider.swift */,
186AD17C27F72E5200048C8E /* MemberDetailProviderManager.swift */,
); );
path = Members; path = Members;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -955,6 +958,7 @@
189E496527F4777400D86BA3 /* NoticeRoomTimelineView.swift in Sources */, 189E496527F4777400D86BA3 /* NoticeRoomTimelineView.swift in Sources */,
18F2BAED27D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift in Sources */, 18F2BAED27D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift in Sources */,
18DF7C4127E4670600291672 /* RoomTimelineViewProvider.swift in Sources */, 18DF7C4127E4670600291672 /* RoomTimelineViewProvider.swift in Sources */,
186AD17D27F72E5200048C8E /* MemberDetailProviderManager.swift in Sources */,
18F2BB0F27D25B4000DD1988 /* RoomScreen.swift in Sources */, 18F2BB0F27D25B4000DD1988 /* RoomScreen.swift in Sources */,
18F2BAFF27D25B4000DD1988 /* HomeScreenModels.swift in Sources */, 18F2BAFF27D25B4000DD1988 /* HomeScreenModels.swift in Sources */,
183E023427E4A73C00903BED /* MemberDetailsProviderProtocol.swift in Sources */, 183E023427E4A73C00903BED /* MemberDetailsProviderProtocol.swift in Sources */,
+9 -5
View File
@@ -18,6 +18,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
private let keychainController: KeychainControllerProtocol private let keychainController: KeychainControllerProtocol
private let authenticationCoordinator: AuthenticationCoordinator! private let authenticationCoordinator: AuthenticationCoordinator!
private let memberDetailProviderManager: MemberDetailProviderManager
private var loadingActivity: Activity? private var loadingActivity: Activity?
private var errorActivity: Activity? private var errorActivity: Activity?
@@ -31,6 +33,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
navigationRouter = NavigationRouter(navigationController: mainNavigationController) navigationRouter = NavigationRouter(navigationController: mainNavigationController)
memberDetailProviderManager = MemberDetailProviderManager()
guard let bundleIdentifier = Bundle.main.bundleIdentifier else { guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
fatalError("Should have a valid bundle identifier at this point") fatalError("Should have a valid bundle identifier at this point")
} }
@@ -82,8 +86,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
let parameters = HomeScreenCoordinatorParameters(userSession: userSession, let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
mediaProvider: userSession.mediaProvider, mediaProvider: userSession.mediaProvider,
eventBriefFactory: EventBriefFactory(), attributedStringBuilder: AttributedStringBuilder(),
attributedStringBuilder: AttributedStringBuilder()) memberDetailProviderManager: memberDetailProviderManager)
let coordinator = HomeScreenCoordinator(parameters: parameters) let coordinator = HomeScreenCoordinator(parameters: parameters)
coordinator.completion = { [weak self] result in coordinator.completion = { [weak self] result in
@@ -109,16 +113,16 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
return return
} }
let memberDetailsProvider = MemberDetailsProvider(roomProxy: roomProxy) let memberDetailProvider = memberDetailProviderManager.memberDetailProviderForRoomProxy(roomProxy)
let timelineItemFactory = RoomTimelineItemFactory(mediaProvider: userSession.mediaProvider, let timelineItemFactory = RoomTimelineItemFactory(mediaProvider: userSession.mediaProvider,
memberDetailsProvider: memberDetailsProvider, memberDetailProvider: memberDetailProvider,
attributedStringBuilder: AttributedStringBuilder()) attributedStringBuilder: AttributedStringBuilder())
let timelineController = RoomTimelineController(timelineProvider: RoomTimelineProvider(roomProxy: roomProxy), let timelineController = RoomTimelineController(timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
timelineItemFactory: timelineItemFactory, timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider, mediaProvider: userSession.mediaProvider,
memberDetailsProvider: memberDetailsProvider) memberDetailProvider: memberDetailProvider)
let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController, let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController,
roomName: roomProxy.name) roomName: roomProxy.name)
@@ -20,8 +20,8 @@ import Combine
struct HomeScreenCoordinatorParameters { struct HomeScreenCoordinatorParameters {
let userSession: UserSession let userSession: UserSession
let mediaProvider: MediaProviderProtocol let mediaProvider: MediaProviderProtocol
let eventBriefFactory: EventBriefFactoryProtocol
let attributedStringBuilder: AttributedStringBuilderProtocol let attributedStringBuilder: AttributedStringBuilderProtocol
let memberDetailProviderManager: MemberDetailProviderManager
} }
enum HomeScreenCoordinatorResult { enum HomeScreenCoordinatorResult {
@@ -96,14 +96,20 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
// MARK: - Private // MARK: - Private
func updateRoomsList() { func updateRoomsList() {
self.roomSummaries = parameters.userSession.rooms.map { roomProxy in self.roomSummaries = parameters.userSession.rooms.compactMap { roomProxy in
guard !roomProxy.isSpace, !roomProxy.isTombstoned else {
return nil
}
if let summary = self.roomSummaries.first(where: { $0.id == roomProxy.id }) { if let summary = self.roomSummaries.first(where: { $0.id == roomProxy.id }) {
return summary return summary
} }
let memberDetailProvider = parameters.memberDetailProviderManager.memberDetailProviderForRoomProxy(roomProxy)
return RoomSummary(roomProxy: roomProxy, return RoomSummary(roomProxy: roomProxy,
mediaProvider: parameters.mediaProvider, mediaProvider: parameters.mediaProvider,
eventBriefFactory: parameters.eventBriefFactory) eventBriefFactory: EventBriefFactory(memberDetailProvider: memberDetailProvider))
} }
self.viewModel.updateWithRoomList(roomSummaries) self.viewModel.updateWithRoomList(roomSummaries)
@@ -36,27 +36,19 @@ struct HomeScreenViewState: BindableState {
var isLoadingRooms: Bool = false var isLoadingRooms: Bool = false
var unencryptedDMs: [HomeScreenRoom] { var unencryptedDMs: [HomeScreenRoom] {
Array(sortedRooms.filter { $0.isDirect && !$0.isEncrypted }) Array(rooms.filter { $0.isDirect && !$0.isEncrypted })
} }
var encryptedDMs: [HomeScreenRoom] { var encryptedDMs: [HomeScreenRoom] {
Array(sortedRooms.filter { $0.isDirect && $0.isEncrypted}) Array(rooms.filter { $0.isDirect && $0.isEncrypted})
} }
var unencryptedRooms: [HomeScreenRoom] { var unencryptedRooms: [HomeScreenRoom] {
Array(sortedRooms.filter { !$0.isDirect && !$0.isEncrypted }) Array(rooms.filter { !$0.isDirect && !$0.isEncrypted })
} }
var encryptedRooms: [HomeScreenRoom] { var encryptedRooms: [HomeScreenRoom] {
Array(sortedRooms.filter { !$0.isDirect && $0.isEncrypted }) Array(rooms.filter { !$0.isDirect && $0.isEncrypted })
}
private var filteredRooms: [HomeScreenRoom] {
rooms.filter { !$0.isSpace && !$0.isTombstoned }
}
private var sortedRooms: [HomeScreenRoom] {
filteredRooms.sorted(by: { ($0.displayName ?? $0.id).lowercased() < ($1.displayName ?? $1.id).lowercased() })
} }
} }
@@ -113,12 +113,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
} }
private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom { private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom {
let lastMessage = lastMessageFromEventBrief(roomSummary.lastMessage) let lastMessage = lastMessageFromEventBrief(roomSummary.lastMessage)
guard var room = self.state.rooms.first(where: { $0.id == roomSummary.id }) else { guard var room = self.state.rooms.first(where: { $0.id == roomSummary.id }) else {
return HomeScreenRoom(id: roomSummary.id, return HomeScreenRoom(id: roomSummary.id,
displayName: roomSummary.name, displayName: roomSummary.displayName ?? roomSummary.name,
topic: roomSummary.topic, topic: roomSummary.topic,
lastMessage: lastMessage, lastMessage: lastMessage,
avatar: roomSummary.avatar, avatar: roomSummary.avatar,
@@ -140,11 +139,17 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return nil return nil
} }
let senderDisplayName = senderDisplayNameForBrief(eventBrief)
if let htmlBody = eventBrief.htmlBody, if let htmlBody = eventBrief.htmlBody,
let lastMessageAttributedString = attributedStringBuilder.fromHTML(htmlBody) { let lastMessageAttributedString = attributedStringBuilder.fromHTML(htmlBody) {
return "\(eventBrief.senderName): \(String(lastMessageAttributedString.characters))" return "\(senderDisplayName): \(String(lastMessageAttributedString.characters))"
} else { } else {
return "\(eventBrief.senderName): \(eventBrief.body)" return "\(senderDisplayName): \(eventBrief.body)"
} }
} }
private func senderDisplayNameForBrief(_ brief: EventBrief) -> String {
brief.senderDisplayName ?? brief.senderId
}
} }
@@ -115,23 +115,23 @@ struct RoomCell: View {
.frame(width: 40, height: 40) .frame(width: 40, height: 40)
} }
VStack(alignment: .leading, spacing: 4.0) { VStack(alignment: .leading, spacing: 2.0) {
Text(roomName(room)) Text(roomName(room))
.font(.headline) .foregroundStyle(.primary)
.fontWeight(.regular)
if let roomTopic = room.topic, roomTopic.count > 0 { if let roomTopic = room.topic, roomTopic.count > 0 {
Text(roomTopic) Text(roomTopic)
.font(.footnote) .font(.footnote.weight(.semibold))
.fontWeight(.bold)
.lineLimit(1) .lineLimit(1)
.foregroundStyle(.secondary)
} }
if let lastMessage = room.lastMessage { if let lastMessage = room.lastMessage {
Text(lastMessage) Text(lastMessage)
.font(.footnote) .font(.callout)
.fontWeight(.medium)
.lineLimit(1) .lineLimit(1)
.foregroundStyle(.secondary)
.padding(.top, 2)
} }
} }
} }
@@ -156,9 +156,16 @@ struct HomeScreen_Previews: PreviewProvider {
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
attributedStringBuilder: AttributedStringBuilder()) attributedStringBuilder: AttributedStringBuilder())
let rooms = [MockRoomSummary(displayName: "Alpha"), let eventBrief = EventBrief(eventId: "id",
senderId: "senderId",
senderDisplayName: "Sender",
body: "Some message",
htmlBody: nil,
date: .now)
let rooms = [MockRoomSummary(topic: "Topic", displayName: "Alpha"),
MockRoomSummary(displayName: "Beta"), MockRoomSummary(displayName: "Beta"),
MockRoomSummary(displayName: "Omega")] MockRoomSummary(displayName: "Omega", lastMessage: eventBrief)]
viewModel.updateWithRoomList(rooms) viewModel.updateWithRoomList(rooms)
@@ -0,0 +1,24 @@
//
// MemberDetailProviderManager.swift
// ElementX
//
// Created by Stefan Ceriu on 01/04/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
class MemberDetailProviderManager {
private var memberDetailProviders: [String: MemberDetailProviderProtocol] = [:]
func memberDetailProviderForRoomProxy(_ roomProxy: RoomProxyProtocol) -> MemberDetailProviderProtocol {
if let memberDetailProvider = memberDetailProviders[roomProxy.id] {
return memberDetailProvider
}
let memberDetailProvider = MemberDetailProvider(roomProxy: roomProxy)
memberDetailProviders[roomProxy.id] = memberDetailProvider
return memberDetailProvider
}
}
@@ -1,5 +1,5 @@
// //
// MemberDetailsProvider.swift // MemberDetailProvider.swift
// ElementX // ElementX
// //
// Created by Stefan Ceriu on 18/03/2022. // Created by Stefan Ceriu on 18/03/2022.
@@ -8,7 +8,7 @@
import Foundation import Foundation
class MemberDetailsProvider: MemberDetailsProviderProtocol { class MemberDetailProvider: MemberDetailProviderProtocol {
private let roomProxy: RoomProxyProtocol? private let roomProxy: RoomProxyProtocol?
private var memberAvatars = [String: String]() private var memberAvatars = [String: String]()
private var memberDisplayNames = [String: String]() private var memberDisplayNames = [String: String]()
@@ -21,7 +21,7 @@ class MemberDetailsProvider: MemberDetailsProviderProtocol {
self.memberAvatars[userId] self.memberAvatars[userId]
} }
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void) { func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void) {
guard let roomProxy = roomProxy else { guard let roomProxy = roomProxy else {
return return
} }
@@ -49,7 +49,7 @@ class MemberDetailsProvider: MemberDetailsProviderProtocol {
self.memberDisplayNames[userId] self.memberDisplayNames[userId]
} }
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void) { func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void) {
guard let roomProxy = roomProxy else { guard let roomProxy = roomProxy else {
return return
} }
@@ -1,5 +1,5 @@
// //
// MemberDetailsProviderProtocol.swift // MemberDetailProviderProtocol.swift
// ElementX // ElementX
// //
// Created by Stefan Ceriu on 18/03/2022. // Created by Stefan Ceriu on 18/03/2022.
@@ -8,16 +8,16 @@
import Foundation import Foundation
enum MemberDetailsProviderError: Error { enum MemberDetailProviderError: Error {
case invalidRoomProxy case invalidRoomProxy
case failedRetrievingUserAvatarURL case failedRetrievingUserAvatarURL
case failedRetrievingUserDisplayName case failedRetrievingUserDisplayName
} }
protocol MemberDetailsProviderProtocol { protocol MemberDetailProviderProtocol {
func avatarURLForUserId(_ userId: String) -> String? func avatarURLForUserId(_ userId: String) -> String?
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void) func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void)
func displayNameForUserId(_ userId: String) -> String? func displayNameForUserId(_ userId: String) -> String?
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void) func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void)
} }
@@ -9,8 +9,9 @@
import Foundation import Foundation
struct EventBrief { struct EventBrief {
let id: String let eventId: String
let senderName: String let senderId: String
let senderDisplayName: String?
let body: String let body: String
let htmlBody: String? let htmlBody: String?
let date: Date let date: Date
@@ -10,20 +10,27 @@ import Foundation
struct EventBriefFactory: EventBriefFactoryProtocol { struct EventBriefFactory: EventBriefFactoryProtocol {
func eventBriefForMessage(_ message: RoomMessageProtocol?) -> EventBrief? { private let memberDetailProvider: MemberDetailProviderProtocol
init(memberDetailProvider: MemberDetailProviderProtocol) {
self.memberDetailProvider = memberDetailProvider
}
func eventBriefForMessage(_ message: RoomMessageProtocol?, completion: @escaping ((EventBrief?) -> Void)) {
guard let message = message else { guard let message = message else {
return nil completion(nil)
return
} }
switch message { switch message {
case is ImageRoomMessage: case is ImageRoomMessage:
return nil completion(nil)
case let message as TextRoomMessage: case let message as TextRoomMessage:
return buildEventBrief(message: message, htmlBody: message.htmlBody) buildEventBrief(message: message, htmlBody: message.htmlBody, completion: completion)
case let message as NoticeRoomMessage: case let message as NoticeRoomMessage:
return buildEventBrief(message: message, htmlBody: message.htmlBody) buildEventBrief(message: message, htmlBody: message.htmlBody, completion: completion)
case let message as EmoteRoomMessage: case let message as EmoteRoomMessage:
return buildEventBrief(message: message, htmlBody: message.htmlBody) buildEventBrief(message: message, htmlBody: message.htmlBody, completion: completion)
default: default:
fatalError("Unknown room message.") fatalError("Unknown room message.")
} }
@@ -31,11 +38,26 @@ struct EventBriefFactory: EventBriefFactoryProtocol {
// MARK: - Private // MARK: - Private
private func buildEventBrief(message: RoomMessageProtocol, htmlBody: String?) -> EventBrief { private func buildEventBrief(message: RoomMessageProtocol, htmlBody: String?, completion: @escaping ((EventBrief?) -> Void)) {
return EventBrief(id: message.id, memberDetailProvider.displayNameForUserId(message.sender) { result in
senderName: message.sender, switch result {
body: message.body, case .success(let displayName):
htmlBody: htmlBody, completion(EventBrief(eventId: message.id,
date: message.originServerTs) senderId: message.sender,
senderDisplayName: displayName,
body: message.body,
htmlBody: htmlBody,
date: message.originServerTs))
case .failure(let error):
MXLog.error("Failed fetching sender display name with error: \(error)")
completion(EventBrief(eventId: message.id,
senderId: message.sender,
senderDisplayName: nil,
body: message.body,
htmlBody: htmlBody,
date: message.originServerTs))
}
}
} }
} }
@@ -9,5 +9,5 @@
import Foundation import Foundation
protocol EventBriefFactoryProtocol { protocol EventBriefFactoryProtocol {
func eventBriefForMessage(_ message: RoomMessageProtocol?) -> EventBrief? func eventBriefForMessage(_ message: RoomMessageProtocol?, completion: @escaping ((EventBrief?) -> Void))
} }
@@ -71,7 +71,9 @@ class RoomSummary: RoomSummaryProtocol {
self.mediaProvider = mediaProvider self.mediaProvider = mediaProvider
self.eventBriefFactory = eventBriefFactory self.eventBriefFactory = eventBriefFactory
lastMessage = eventBriefFactory.eventBriefForMessage(roomProxy.messages.last) eventBriefFactory.eventBriefForMessage(roomProxy.messages.last) { [weak self] result in
self?.lastMessage = result
}
roomProxy.callbacks.sink { [weak self] callback in roomProxy.callbacks.sink { [weak self] callback in
guard let self = self else { guard let self = self else {
@@ -80,7 +82,9 @@ class RoomSummary: RoomSummaryProtocol {
switch callback { switch callback {
case .updatedMessages: case .updatedMessages:
self.lastMessage = self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last) self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last) { [weak self] result in
self?.lastMessage = result
}
} }
} }
.store(in: &roomUpdateListeners) .store(in: &roomUpdateListeners)
@@ -120,7 +124,9 @@ class RoomSummary: RoomSummaryProtocol {
switch result { switch result {
case .success: case .success:
self.lastMessage = self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last) self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last) { [weak self] result in
self?.lastMessage = result
}
case .failure(let error): case .failure(let error):
MXLog.error("Failed back paginating with error: \(error)") MXLog.error("Failed back paginating with error: \(error)")
} }
@@ -14,7 +14,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
private let timelineProvider: RoomTimelineProviderProtocol private let timelineProvider: RoomTimelineProviderProtocol
private let timelineItemFactory: RoomTimelineItemFactory private let timelineItemFactory: RoomTimelineItemFactory
private let mediaProvider: MediaProviderProtocol private let mediaProvider: MediaProviderProtocol
private let memberDetailsProvider: MemberDetailsProviderProtocol private let memberDetailProvider: MemberDetailProviderProtocol
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@@ -25,11 +25,11 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
init(timelineProvider: RoomTimelineProviderProtocol, init(timelineProvider: RoomTimelineProviderProtocol,
timelineItemFactory: RoomTimelineItemFactory, timelineItemFactory: RoomTimelineItemFactory,
mediaProvider: MediaProviderProtocol, mediaProvider: MediaProviderProtocol,
memberDetailsProvider: MemberDetailsProviderProtocol) { memberDetailProvider: MemberDetailProviderProtocol) {
self.timelineProvider = timelineProvider self.timelineProvider = timelineProvider
self.timelineItemFactory = timelineItemFactory self.timelineItemFactory = timelineItemFactory
self.mediaProvider = mediaProvider self.mediaProvider = mediaProvider
self.memberDetailsProvider = memberDetailsProvider self.memberDetailProvider = memberDetailProvider
self.timelineProvider.callbacks.sink { [weak self] callback in self.timelineProvider.callbacks.sink { [weak self] callback in
guard let self = self else { return } guard let self = self else { return }
@@ -152,7 +152,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
return return
} }
memberDetailsProvider.avatarURLForUserId(timelineItem.senderId) { result in memberDetailProvider.avatarURLForUserId(timelineItem.senderId) { result in
if case let .success(avatarURL) = result, if case let .success(avatarURL) = result,
let avatarURL = avatarURL { let avatarURL = avatarURL {
self.mediaProvider.loadImageFromURL(avatarURL) { result in self.mediaProvider.loadImageFromURL(avatarURL) { result in
@@ -176,7 +176,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
return return
} }
memberDetailsProvider.displayNameForUserId(timelineItem.senderId) { result in memberDetailProvider.displayNameForUserId(timelineItem.senderId) { result in
if case let .success(displayName) = result, if case let .success(displayName) = result,
let displayName = displayName { let displayName = displayName {
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }), guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
@@ -11,20 +11,20 @@ import UIKit
struct RoomTimelineItemFactory { struct RoomTimelineItemFactory {
private let mediaProvider: MediaProviderProtocol private let mediaProvider: MediaProviderProtocol
private let memberDetailsProvider: MemberDetailsProviderProtocol private let memberDetailProvider: MemberDetailProviderProtocol
private let attributedStringBuilder: AttributedStringBuilderProtocol private let attributedStringBuilder: AttributedStringBuilderProtocol
init(mediaProvider: MediaProviderProtocol, init(mediaProvider: MediaProviderProtocol,
memberDetailsProvider: MemberDetailsProviderProtocol, memberDetailProvider: MemberDetailProviderProtocol,
attributedStringBuilder: AttributedStringBuilderProtocol) { attributedStringBuilder: AttributedStringBuilderProtocol) {
self.mediaProvider = mediaProvider self.mediaProvider = mediaProvider
self.memberDetailsProvider = memberDetailsProvider self.memberDetailProvider = memberDetailProvider
self.attributedStringBuilder = attributedStringBuilder self.attributedStringBuilder = attributedStringBuilder
} }
func buildTimelineItemFor(_ roomMessage: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol { func buildTimelineItemFor(_ roomMessage: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol {
let displayName = memberDetailsProvider.displayNameForUserId(roomMessage.sender) let displayName = memberDetailProvider.displayNameForUserId(roomMessage.sender)
let avatarURL = memberDetailsProvider.avatarURLForUserId(roomMessage.sender) let avatarURL = memberDetailProvider.avatarURLForUserId(roomMessage.sender)
let avatarImage = mediaProvider.imageForURL(avatarURL) let avatarImage = mediaProvider.imageForURL(avatarURL)
switch roomMessage { switch roomMessage {