Skip to content

Commit

Permalink
Caching memberDetailProviders, showing sender display names in home s…
Browse files Browse the repository at this point in the history
…creen last message, cleaner homescreen design.
  • Loading branch information
stefanceriu committed Apr 1, 2022
1 parent 86d85f4 commit 711857a
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 70 deletions.
4 changes: 4 additions & 0 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
1850257127B6A135002E6B18 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1850256927B6A135002E6B18 /* LaunchScreen.storyboard */; };
1863A3FC27BA5A9100B52E4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A3FB27BA5A9100B52E4D /* KeychainAccess */; };
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 */; };
18920C0F27F233FF00A717B5 /* EmoteRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C0D27F233FF00A717B5 /* EmoteRoomMessage.swift */; };
18920C1227F2347600A717B5 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C1027F2347600A717B5 /* NoticeRoomTimelineItem.swift */; };
Expand Down Expand Up @@ -166,6 +167,7 @@
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>"; };
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>"; };
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>"; };
Expand Down Expand Up @@ -487,6 +489,7 @@
children = (
183E023327E4A73C00903BED /* MemberDetailsProviderProtocol.swift */,
18DF7C5227E4754500291672 /* MemberDetailsProvider.swift */,
186AD17C27F72E5200048C8E /* MemberDetailProviderManager.swift */,
);
path = Members;
sourceTree = "<group>";
Expand Down Expand Up @@ -955,6 +958,7 @@
189E496527F4777400D86BA3 /* NoticeRoomTimelineView.swift in Sources */,
18F2BAED27D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift in Sources */,
18DF7C4127E4670600291672 /* RoomTimelineViewProvider.swift in Sources */,
186AD17D27F72E5200048C8E /* MemberDetailProviderManager.swift in Sources */,
18F2BB0F27D25B4000DD1988 /* RoomScreen.swift in Sources */,
18F2BAFF27D25B4000DD1988 /* HomeScreenModels.swift in Sources */,
183E023427E4A73C00903BED /* MemberDetailsProviderProtocol.swift in Sources */,
Expand Down
14 changes: 9 additions & 5 deletions ElementX/Sources/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
private let keychainController: KeychainControllerProtocol
private let authenticationCoordinator: AuthenticationCoordinator!

private let memberDetailProviderManager: MemberDetailProviderManager

private var loadingActivity: Activity?
private var errorActivity: Activity?

Expand All @@ -31,6 +33,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {

navigationRouter = NavigationRouter(navigationController: mainNavigationController)

memberDetailProviderManager = MemberDetailProviderManager()

guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
fatalError("Should have a valid bundle identifier at this point")
}
Expand Down Expand Up @@ -82,8 +86,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {

let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
mediaProvider: userSession.mediaProvider,
eventBriefFactory: EventBriefFactory(),
attributedStringBuilder: AttributedStringBuilder())
attributedStringBuilder: AttributedStringBuilder(),
memberDetailProviderManager: memberDetailProviderManager)
let coordinator = HomeScreenCoordinator(parameters: parameters)

coordinator.completion = { [weak self] result in
Expand All @@ -109,16 +113,16 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
return
}

let memberDetailsProvider = MemberDetailsProvider(roomProxy: roomProxy)
let memberDetailProvider = memberDetailProviderManager.memberDetailProviderForRoomProxy(roomProxy)

let timelineItemFactory = RoomTimelineItemFactory(mediaProvider: userSession.mediaProvider,
memberDetailsProvider: memberDetailsProvider,
memberDetailProvider: memberDetailProvider,
attributedStringBuilder: AttributedStringBuilder())

let timelineController = RoomTimelineController(timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider,
memberDetailsProvider: memberDetailsProvider)
memberDetailProvider: memberDetailProvider)

let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController,
roomName: roomProxy.name)
Expand Down
12 changes: 9 additions & 3 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import Combine
struct HomeScreenCoordinatorParameters {
let userSession: UserSession
let mediaProvider: MediaProviderProtocol
let eventBriefFactory: EventBriefFactoryProtocol
let attributedStringBuilder: AttributedStringBuilderProtocol
let memberDetailProviderManager: MemberDetailProviderManager
}

enum HomeScreenCoordinatorResult {
Expand Down Expand Up @@ -96,14 +96,20 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
// MARK: - Private

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 }) {
return summary
}

let memberDetailProvider = parameters.memberDetailProviderManager.memberDetailProviderForRoomProxy(roomProxy)

return RoomSummary(roomProxy: roomProxy,
mediaProvider: parameters.mediaProvider,
eventBriefFactory: parameters.eventBriefFactory)
eventBriefFactory: EventBriefFactory(memberDetailProvider: memberDetailProvider))
}

self.viewModel.updateWithRoomList(roomSummaries)
Expand Down
16 changes: 4 additions & 12 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,19 @@ struct HomeScreenViewState: BindableState {
var isLoadingRooms: Bool = false

var unencryptedDMs: [HomeScreenRoom] {
Array(sortedRooms.filter { $0.isDirect && !$0.isEncrypted })
Array(rooms.filter { $0.isDirect && !$0.isEncrypted })
}

var encryptedDMs: [HomeScreenRoom] {
Array(sortedRooms.filter { $0.isDirect && $0.isEncrypted})
Array(rooms.filter { $0.isDirect && $0.isEncrypted})
}

var unencryptedRooms: [HomeScreenRoom] {
Array(sortedRooms.filter { !$0.isDirect && !$0.isEncrypted })
Array(rooms.filter { !$0.isDirect && !$0.isEncrypted })
}

var encryptedRooms: [HomeScreenRoom] {
Array(sortedRooms.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() })
Array(rooms.filter { !$0.isDirect && $0.isEncrypted })
}
}

Expand Down
13 changes: 9 additions & 4 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
}

private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom {

let lastMessage = lastMessageFromEventBrief(roomSummary.lastMessage)

guard var room = self.state.rooms.first(where: { $0.id == roomSummary.id }) else {
return HomeScreenRoom(id: roomSummary.id,
displayName: roomSummary.name,
displayName: roomSummary.displayName ?? roomSummary.name,
topic: roomSummary.topic,
lastMessage: lastMessage,
avatar: roomSummary.avatar,
Expand All @@ -140,11 +139,17 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return nil
}

let senderDisplayName = senderDisplayNameForBrief(eventBrief)

if let htmlBody = eventBrief.htmlBody,
let lastMessageAttributedString = attributedStringBuilder.fromHTML(htmlBody) {
return "\(eventBrief.senderName): \(String(lastMessageAttributedString.characters))"
return "\(senderDisplayName): \(String(lastMessageAttributedString.characters))"
} else {
return "\(eventBrief.senderName): \(eventBrief.body)"
return "\(senderDisplayName): \(eventBrief.body)"
}
}

private func senderDisplayNameForBrief(_ brief: EventBrief) -> String {
brief.senderDisplayName ?? brief.senderId
}
}
25 changes: 16 additions & 9 deletions ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,23 +115,23 @@ struct RoomCell: View {
.frame(width: 40, height: 40)
}

VStack(alignment: .leading, spacing: 4.0) {
VStack(alignment: .leading, spacing: 2.0) {
Text(roomName(room))
.font(.headline)
.fontWeight(.regular)
.foregroundStyle(.primary)

if let roomTopic = room.topic, roomTopic.count > 0 {
Text(roomTopic)
.font(.footnote)
.fontWeight(.bold)
.font(.footnote.weight(.semibold))
.lineLimit(1)
.foregroundStyle(.secondary)
}

if let lastMessage = room.lastMessage {
Text(lastMessage)
.font(.footnote)
.fontWeight(.medium)
.font(.callout)
.lineLimit(1)
.foregroundStyle(.secondary)
.padding(.top, 2)
}
}
}
Expand All @@ -156,9 +156,16 @@ struct HomeScreen_Previews: PreviewProvider {
mediaProvider: MockMediaProvider(),
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: "Omega")]
MockRoomSummary(displayName: "Omega", lastMessage: eventBrief)]

viewModel.updateWithRoomList(rooms)

Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// MemberDetailsProvider.swift
// MemberDetailProvider.swift
// ElementX
//
// Created by Stefan Ceriu on 18/03/2022.
Expand All @@ -8,7 +8,7 @@

import Foundation

class MemberDetailsProvider: MemberDetailsProviderProtocol {
class MemberDetailProvider: MemberDetailProviderProtocol {
private let roomProxy: RoomProxyProtocol?
private var memberAvatars = [String: String]()
private var memberDisplayNames = [String: String]()
Expand All @@ -21,7 +21,7 @@ class MemberDetailsProvider: MemberDetailsProviderProtocol {
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 {
return
}
Expand Down Expand Up @@ -49,7 +49,7 @@ class MemberDetailsProvider: MemberDetailsProviderProtocol {
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 {
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// MemberDetailsProviderProtocol.swift
// MemberDetailProviderProtocol.swift
// ElementX
//
// Created by Stefan Ceriu on 18/03/2022.
Expand All @@ -8,16 +8,16 @@

import Foundation

enum MemberDetailsProviderError: Error {
enum MemberDetailProviderError: Error {
case invalidRoomProxy
case failedRetrievingUserAvatarURL
case failedRetrievingUserDisplayName
}

protocol MemberDetailsProviderProtocol {
protocol MemberDetailProviderProtocol {
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, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void)
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void)
}
5 changes: 3 additions & 2 deletions ElementX/Sources/Services/Room/RoomSummary/EventBrief.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import Foundation

struct EventBrief {
let id: String
let senderName: String
let eventId: String
let senderId: String
let senderDisplayName: String?
let body: String
let htmlBody: String?
let date: Date
Expand Down
46 changes: 34 additions & 12 deletions ElementX/Sources/Services/Room/RoomSummary/EventBriefFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,54 @@ import Foundation

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 {
return nil
completion(nil)
return
}

switch message {
case is ImageRoomMessage:
return nil
completion(nil)
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:
return buildEventBrief(message: message, htmlBody: message.htmlBody)
buildEventBrief(message: message, htmlBody: message.htmlBody, completion: completion)
case let message as EmoteRoomMessage:
return buildEventBrief(message: message, htmlBody: message.htmlBody)
buildEventBrief(message: message, htmlBody: message.htmlBody, completion: completion)
default:
fatalError("Unknown room message.")
}
}

// MARK: - Private

private func buildEventBrief(message: RoomMessageProtocol, htmlBody: String?) -> EventBrief {
return EventBrief(id: message.id,
senderName: message.sender,
body: message.body,
htmlBody: htmlBody,
date: message.originServerTs)
private func buildEventBrief(message: RoomMessageProtocol, htmlBody: String?, completion: @escaping ((EventBrief?) -> Void)) {
memberDetailProvider.displayNameForUserId(message.sender) { result in
switch result {
case .success(let displayName):
completion(EventBrief(eventId: message.id,
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))
}
}
}
}
Loading

0 comments on commit 711857a

Please sign in to comment.