diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 486c205ca..19eb4b7ff 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 181A317727F703E300B8305B /* RoomSummaryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181A317427F703E300B8305B /* RoomSummaryProtocol.swift */; }; + 181A317827F703E300B8305B /* RoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181A317527F703E300B8305B /* RoomSummary.swift */; }; + 181A317927F703E300B8305B /* MockRoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181A317627F703E300B8305B /* MockRoomSummary.swift */; }; + 181A317B27F70AEB00B8305B /* EventBrief.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181A317A27F70AEB00B8305B /* EventBrief.swift */; }; + 181A317D27F70B1E00B8305B /* EventBriefFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181A317C27F70B1E00B8305B /* EventBriefFactory.swift */; }; + 181A317F27F70CCA00B8305B /* EventBriefFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181A317E27F70CCA00B8305B /* EventBriefFactoryProtocol.swift */; }; 182BC48127C4EBBB00A30C33 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 182BC48027C4EBBB00A30C33 /* Kingfisher */; }; 183E023427E4A73C00903BED /* MemberDetailsProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183E023327E4A73C00903BED /* MemberDetailsProviderProtocol.swift */; }; 183E023627E4A79D00903BED /* RoomTimelineProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183E023527E4A79D00903BED /* RoomTimelineProviderProtocol.swift */; }; @@ -141,6 +147,12 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 181A317427F703E300B8305B /* RoomSummaryProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomSummaryProtocol.swift; sourceTree = ""; }; + 181A317527F703E300B8305B /* RoomSummary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = ""; }; + 181A317627F703E300B8305B /* MockRoomSummary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockRoomSummary.swift; sourceTree = ""; }; + 181A317A27F70AEB00B8305B /* EventBrief.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBrief.swift; sourceTree = ""; }; + 181A317C27F70B1E00B8305B /* EventBriefFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactory.swift; sourceTree = ""; }; + 181A317E27F70CCA00B8305B /* EventBriefFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactoryProtocol.swift; sourceTree = ""; }; 183E023327E4A73C00903BED /* MemberDetailsProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailsProviderProtocol.swift; sourceTree = ""; }; 183E023527E4A79D00903BED /* RoomTimelineProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProviderProtocol.swift; sourceTree = ""; }; 1850252427B6918C002E6B18 /* ElementX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -288,6 +300,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 181A317327F703E300B8305B /* RoomSummary */ = { + isa = PBXGroup; + children = ( + 181A317427F703E300B8305B /* RoomSummaryProtocol.swift */, + 181A317527F703E300B8305B /* RoomSummary.swift */, + 181A317627F703E300B8305B /* MockRoomSummary.swift */, + 181A317E27F70CCA00B8305B /* EventBriefFactoryProtocol.swift */, + 181A317C27F70B1E00B8305B /* EventBriefFactory.swift */, + 181A317A27F70AEB00B8305B /* EventBrief.swift */, + ); + path = RoomSummary; + sourceTree = ""; + }; 1850251B27B6918C002E6B18 = { isa = PBXGroup; children = ( @@ -398,12 +423,13 @@ 18C5744727E1D84000D70937 /* Room */ = { isa = PBXGroup; children = ( - 18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */, - 18C5744A27E1D84000D70937 /* RoomProxy.swift */, 18C5744B27E1D84000D70937 /* MockRoomProxy.swift */, 18C5745527E1DCA800D70937 /* RoomMessageFactory.swift */, + 18C5744A27E1D84000D70937 /* RoomProxy.swift */, + 18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */, 18DF7C5127E4753A00291672 /* Members */, 18C5744927E1D84000D70937 /* Messages */, + 181A317327F703E300B8305B /* RoomSummary */, ); path = Room; sourceTree = ""; @@ -924,6 +950,7 @@ files = ( 18F2BADE27D25B4000DD1988 /* KeychainControllerProtocol.swift in Sources */, 18C5745027E1D87800D70937 /* RoomMessageProtocol.swift in Sources */, + 181A317727F703E300B8305B /* RoomSummaryProtocol.swift in Sources */, 18DF7C4C27E4672C00291672 /* EventBasedTimelineItemProtocol.swift in Sources */, 189E496527F4777400D86BA3 /* NoticeRoomTimelineView.swift in Sources */, 18F2BAED27D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift in Sources */, @@ -944,6 +971,7 @@ 18F2BB1227D25B4000DD1988 /* LoginScreenViewModel.swift in Sources */, 18F2BAE727D25B4000DD1988 /* RoundedToastView.swift in Sources */, 18F2BAF227D25B4000DD1988 /* WeakDictionaryKeyReference.swift in Sources */, + 181A317827F703E300B8305B /* RoomSummary.swift in Sources */, 183E023627E4A79D00903BED /* RoomTimelineProviderProtocol.swift in Sources */, 18DF7C4727E4670600291672 /* TextRoomTimelineItem.swift in Sources */, 18F2BAE027D25B4000DD1988 /* NavigationRouter.swift in Sources */, @@ -963,6 +991,8 @@ 18C5745627E1DCA800D70937 /* RoomMessageFactory.swift in Sources */, 18F2BB1127D25B4000DD1988 /* RoomScreenModels.swift in Sources */, 18F2BADB27D25B4000DD1988 /* AuthenticationCoordinator.swift in Sources */, + 181A317B27F70AEB00B8305B /* EventBrief.swift in Sources */, + 181A317927F703E300B8305B /* MockRoomSummary.swift in Sources */, 1850256F27B6A135002E6B18 /* AppDelegate.swift in Sources */, 18F2BAE627D25B4000DD1988 /* NavigationRouterType.swift in Sources */, 18F2BAE927D25B4000DD1988 /* ActivityPresentable.swift in Sources */, @@ -974,6 +1004,7 @@ 18DF7C5327E4754500291672 /* MemberDetailsProvider.swift in Sources */, 18F2BAE227D25B4000DD1988 /* NavigationRouterStoreProtocol.swift in Sources */, 18DDB72E27EF936D000F1ABF /* MatrixEntitityRegex.swift in Sources */, + 181A317D27F70B1E00B8305B /* EventBriefFactory.swift in Sources */, 18F2BB1727D25B4000DD1988 /* LoginScreenCoordinator.swift in Sources */, 18F2BAF527D25B4000DD1988 /* WeakKeyDictionary.swift in Sources */, 18F2BADF27D25B4000DD1988 /* NavigationRouterStore.swift in Sources */, @@ -995,6 +1026,7 @@ 18F2BADD27D25B4000DD1988 /* KeychainController.swift in Sources */, 18920C1227F2347600A717B5 /* NoticeRoomTimelineItem.swift in Sources */, 18920C0E27F233FF00A717B5 /* NoticeRoomMessage.swift in Sources */, + 181A317F27F70CCA00B8305B /* EventBriefFactoryProtocol.swift in Sources */, 18F2BAFB27D25B4000DD1988 /* HomeScreenCoordinator.swift in Sources */, 18F2BB0C27D25B4000DD1988 /* RoomScreenCoordinator.swift in Sources */, 18DDB72127EB9D57000F1ABF /* ElementXAttributeScope.swift in Sources */, diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/AppCoordinator.swift index 76ca4bbd1..855fa6120 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/AppCoordinator.swift @@ -80,7 +80,10 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { fatalError("User session should be already setup at this point") } - let parameters = HomeScreenCoordinatorParameters(userSession: userSession, mediaProvider: userSession.mediaProvider) + let parameters = HomeScreenCoordinatorParameters(userSession: userSession, + mediaProvider: userSession.mediaProvider, + eventBriefFactory: EventBriefFactory(), + attributedStringBuilder: AttributedStringBuilder()) let coordinator = HomeScreenCoordinator(parameters: parameters) coordinator.completion = { [weak self] result in diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index 7bb92d8b2..8b4f5bfaf 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -20,6 +20,8 @@ import Combine struct HomeScreenCoordinatorParameters { let userSession: UserSession let mediaProvider: MediaProviderProtocol + let eventBriefFactory: EventBriefFactoryProtocol + let attributedStringBuilder: AttributedStringBuilderProtocol } enum HomeScreenCoordinatorResult { @@ -37,6 +39,8 @@ final class HomeScreenCoordinator: Coordinator, Presentable { private let hostingController: UIViewController private var viewModel: HomeScreenViewModelProtocol + private var roomSummaries: [RoomSummary] = [] + private var cancellables = Set() // MARK: Public @@ -50,10 +54,11 @@ final class HomeScreenCoordinator: Coordinator, Presentable { init(parameters: HomeScreenCoordinatorParameters) { self.parameters = parameters - let userDisplayName = self.parameters.userSession.userDisplayName ?? self.parameters.userSession.userIdentifier + let userDisplayName = parameters.userSession.userDisplayName ?? parameters.userSession.userIdentifier viewModel = HomeScreenViewModel(userDisplayName: userDisplayName, - userAvatarURL: self.parameters.userSession.userAvatarURL, - mediaProvider: self.parameters.mediaProvider) + userAvatarURL: parameters.userSession.userAvatarURL, + mediaProvider: parameters.mediaProvider, + attributedStringBuilder: parameters.attributedStringBuilder) let view = HomeScreen(context: viewModel.context) hostingController = UIHostingController(rootView: view) @@ -91,6 +96,16 @@ final class HomeScreenCoordinator: Coordinator, Presentable { // MARK: - Private func updateRoomsList() { - self.viewModel.updateWithRoomList(parameters.userSession.rooms) + self.roomSummaries = parameters.userSession.rooms.map { roomProxy in + if let summary = self.roomSummaries.first(where: { $0.id == roomProxy.id }) { + return summary + } + + return RoomSummary(roomProxy: roomProxy, + mediaProvider: parameters.mediaProvider, + eventBriefFactory: parameters.eventBriefFactory) + } + + self.viewModel.updateWithRoomList(roomSummaries) } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 3722ff303..2337e6eba 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -25,9 +25,11 @@ typealias HomeScreenViewModelType = StateStoreViewModel() - private var roomList: [RoomProxyProtocol]? { + + private var roomList: [RoomSummaryProtocol]? { didSet { self.state.isLoadingRooms = (roomList?.count ?? 0 == 0) } @@ -37,8 +39,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol // MARK: - Setup - init(userDisplayName: String, userAvatarURL: String?, mediaProvider: MediaProviderProtocol) { + init(userDisplayName: String, + userAvatarURL: String?, + mediaProvider: MediaProviderProtocol, + attributedStringBuilder: AttributedStringBuilderProtocol) { self.mediaProvider = mediaProvider + self.attributedStringBuilder = attributedStringBuilder + super.init(initialViewState: HomeScreenViewState(userDisplayName: userDisplayName, isLoadingRooms: true)) if let userAvatarURL = userAvatarURL { @@ -63,24 +70,31 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } } - func updateWithRoomList(_ roomList: [RoomProxyProtocol]) { + func updateWithRoomList(_ roomList: [RoomSummaryProtocol]) { self.roomList = roomList - state.rooms = roomList.map { roomProxy in - buildRoomFromProxy(roomProxy) + state.rooms = roomList.map { roomSummary in + buildOrUpdateRoomFromSummary(roomSummary) } roomUpdateListeners.removeAll() - roomList.forEach({ roomProxy in - roomProxy.callbacks.sink { [weak self] callback in + roomList.forEach({ roomSummary in + roomSummary.callbacks.sink { [weak self] callback in + guard let self = self else { + return + } + switch callback { - case .updatedMessages: - self?.loadLastMessageForRoomWithIdentifier(roomProxy.id) + case .updatedData: + if let index = self.state.rooms.firstIndex(where: { $0.id == roomSummary.id }) { + self.state.rooms[index] = self.buildOrUpdateRoomFromSummary(roomSummary) + } } } .store(in: &roomUpdateListeners) }) + } func updateWithUserAvatar(_ avatar: UIImage?) { @@ -90,100 +104,47 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol // MARK: - Private private func loadRoomDataForIdentifier(_ roomIdentifier: String) { - let room = state.rooms.first(where: { $0.id == roomIdentifier }) - - if room?.avatar == nil { - loadAvatarForRoomWithIdentifier(roomIdentifier) - } - - if room?.displayName == nil { - loadRoomDisplayNameForRoomWithIdentifier(roomIdentifier) - } - - if room?.lastMessage == nil { - loadLastMessageForRoomWithIdentifier(roomIdentifier) - } - } - - private func loadAvatarForRoomWithIdentifier(_ roomIdentifier: String) { - guard let room = roomList?.filter({ $0.id == roomIdentifier }).first, - let avatarURLString = room.avatarURL else { - return - } - - mediaProvider.loadImageFromURL(avatarURLString) { [weak self] result in - guard let self = self else { return } - if case let .success(image) = result { - self.updateAvatar(image, forRoomWithIdentifier: roomIdentifier) - } - } - } - - private func updateAvatar(_ avatar: UIImage?, forRoomWithIdentifier roomIdentifier: String) { - guard let index = self.state.rooms.firstIndex(where: { $0.id == roomIdentifier }) else { + guard let roomSummary = roomList?.first(where: { $0.id == roomIdentifier }) else { + MXLog.error("Invalid room identifier") return } - self.state.rooms[index].avatar = avatar + roomSummary.loadData() } - private func loadRoomDisplayNameForRoomWithIdentifier(_ roomIdentifier: String) { - guard let room = roomList?.filter({ $0.id == roomIdentifier }).first else { - return + 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, + topic: roomSummary.topic, + lastMessage: lastMessage, + avatar: roomSummary.avatar, + isDirect: roomSummary.isDirect, + isEncrypted: roomSummary.isEncrypted, + isSpace: roomSummary.isSpace, + isTombstoned: roomSummary.isTombstoned) } - room.displayName { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(let displayName): - self.updateDisplayName(displayName, forRoomWithIdentifier: roomIdentifier) - default: - break - } - } + room.avatar = roomSummary.avatar + room.displayName = roomSummary.displayName + room.lastMessage = lastMessage + + return room } - private func updateDisplayName(_ displayName: String, forRoomWithIdentifier roomIdentifier: String) { - guard let index = self.state.rooms.firstIndex(where: { $0.id == roomIdentifier }) else { - return + private func lastMessageFromEventBrief(_ eventBrief: EventBrief?) -> String? { + guard let eventBrief = eventBrief else { + return nil } - self.state.rooms[index].displayName = displayName - } - - private func loadLastMessageForRoomWithIdentifier(_ roomIdentifier: String) { - guard let room = roomList?.filter({ $0.id == roomIdentifier }).first else { - return + if let htmlBody = eventBrief.htmlBody, + let lastMessageAttributedString = attributedStringBuilder.fromHTML(htmlBody) { + return "\(eventBrief.senderName): \(String(lastMessageAttributedString.characters))" + } else { + return "\(eventBrief.senderName): \(eventBrief.body)" } - - if let lastMessage = room.messages.last { - self.updateLastMessage(lastMessage.body, forRoomWithIdentifier: roomIdentifier) - return - } - - room.paginateBackwards(count: 1, callback: nil) - } - - private func updateLastMessage(_ lastMessage: String, forRoomWithIdentifier roomIdentifier: String) { - guard let index = self.state.rooms.firstIndex(where: { $0.id == roomIdentifier }) else { - return - } - - self.state.rooms[index].lastMessage = lastMessage - } - - private func buildRoomFromProxy(_ roomProxy: RoomProxyProtocol) -> HomeScreenRoom { - let avatar = mediaProvider.imageForURL(roomProxy.avatarURL) - - return HomeScreenRoom(id: roomProxy.id, - displayName: roomProxy.name, - topic: roomProxy.topic, - lastMessage: roomProxy.messages.last?.body, - avatar: avatar, - isDirect: roomProxy.isDirect, - isEncrypted: roomProxy.isEncrypted, - isSpace: roomProxy.isSpace, - isTombstoned: roomProxy.isTombstoned) } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift index 5ba27a652..1fdeaaafb 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift @@ -22,5 +22,5 @@ protocol HomeScreenViewModelProtocol { var context: HomeScreenViewModelType.Context { get } - func updateWithRoomList(_ roomList: [RoomProxyProtocol]) + func updateWithRoomList(_ roomList: [RoomSummaryProtocol]) } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 316f414d5..1c34008a6 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -153,11 +153,12 @@ struct HomeScreen_Previews: PreviewProvider { static var previews: some View { let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed", userAvatarURL: nil, - mediaProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider(), + attributedStringBuilder: AttributedStringBuilder()) - let rooms = [MockRoomProxy(displayName: "Alfa"), - MockRoomProxy(displayName: "Beta"), - MockRoomProxy(displayName: "Omega")] + let rooms = [MockRoomSummary(displayName: "Alpha"), + MockRoomSummary(displayName: "Beta"), + MockRoomSummary(displayName: "Omega")] viewModel.updateWithRoomList(rooms) diff --git a/ElementX/Sources/Services/Room/RoomSummary/EventBrief.swift b/ElementX/Sources/Services/Room/RoomSummary/EventBrief.swift new file mode 100644 index 000000000..cce6b4df7 --- /dev/null +++ b/ElementX/Sources/Services/Room/RoomSummary/EventBrief.swift @@ -0,0 +1,17 @@ +// +// EventBrief.swift +// ElementX +// +// Created by Stefan Ceriu on 01/04/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation + +struct EventBrief { + let id: String + let senderName: String + let body: String + let htmlBody: String? + let date: Date +} diff --git a/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactory.swift b/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactory.swift new file mode 100644 index 000000000..c37b82e66 --- /dev/null +++ b/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactory.swift @@ -0,0 +1,41 @@ +// +// EventBriefFactory.swift +// ElementX +// +// Created by Stefan Ceriu on 01/04/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation + +struct EventBriefFactory: EventBriefFactoryProtocol { + + func eventBriefForMessage(_ message: RoomMessageProtocol?) -> EventBrief? { + guard let message = message else { + return nil + } + + switch message { + case is ImageRoomMessage: + return nil + case let message as TextRoomMessage: + return buildEventBrief(message: message, htmlBody: message.htmlBody) + case let message as NoticeRoomMessage: + return buildEventBrief(message: message, htmlBody: message.htmlBody) + case let message as EmoteRoomMessage: + return buildEventBrief(message: message, htmlBody: message.htmlBody) + 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) + } +} diff --git a/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactoryProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactoryProtocol.swift new file mode 100644 index 000000000..af4baa25a --- /dev/null +++ b/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactoryProtocol.swift @@ -0,0 +1,13 @@ +// +// EventBriefFactoryProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 01/04/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation + +protocol EventBriefFactoryProtocol { + func eventBriefForMessage(_ message: RoomMessageProtocol?) -> EventBrief? +} diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift new file mode 100644 index 000000000..46bfa49dd --- /dev/null +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift @@ -0,0 +1,38 @@ +// +// MockRoomSummary.swift +// ElementX +// +// Created by Stefan Ceriu on 01/04/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import UIKit +import Combine + +struct MockRoomSummary: RoomSummaryProtocol { + var id: String = UUID().uuidString + + var name: String? + + var topic: String? + + var isDirect: Bool = false + + var isEncrypted: Bool = false + + var isSpace: Bool = false + + var isTombstoned: Bool = false + + var displayName: String? + + var lastMessage: EventBrief? + + var avatar: UIImage? + + func loadData() { + + } + + var callbacks = PassthroughSubject() +} diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift new file mode 100644 index 000000000..4bf9d6a6d --- /dev/null +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift @@ -0,0 +1,145 @@ +// +// RoomSummary.swift +// ElementX +// +// Created by Stefan Ceriu on 01/04/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation +import UIKit +import Combine + +class RoomSummary: RoomSummaryProtocol { + private let roomProxy: RoomProxyProtocol + private let mediaProvider: MediaProviderProtocol + private let eventBriefFactory: EventBriefFactoryProtocol + + private var hasLoadedData = false + private var roomUpdateListeners = Set() + + var id: String { + roomProxy.id + } + + var name: String? { + roomProxy.name + } + + var topic: String? { + roomProxy.topic + } + + var isDirect: Bool { + roomProxy.isDirect + } + + var isEncrypted: Bool { + roomProxy.isEncrypted + } + + var isSpace: Bool { + roomProxy.isSpace + } + + var isTombstoned: Bool { + roomProxy.isTombstoned + } + + private(set) var displayName: String? { + didSet { + self.callbacks.send(.updatedData) + } + } + + private(set) var lastMessage: EventBrief? { + didSet { + self.callbacks.send(.updatedData) + } + } + + private(set) var avatar: UIImage? { + didSet { + self.callbacks.send(.updatedData) + } + } + + let callbacks = PassthroughSubject() + + init(roomProxy: RoomProxyProtocol, mediaProvider: MediaProviderProtocol, eventBriefFactory: EventBriefFactoryProtocol) { + self.roomProxy = roomProxy + self.mediaProvider = mediaProvider + self.eventBriefFactory = eventBriefFactory + + lastMessage = eventBriefFactory.eventBriefForMessage(roomProxy.messages.last) + + roomProxy.callbacks.sink { [weak self] callback in + guard let self = self else { + return + } + + switch callback { + case .updatedMessages: + self.lastMessage = self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last) + } + } + .store(in: &roomUpdateListeners) + } + + func loadData() { + if hasLoadedData { + return + } + + loadDisplayName() + loadLastMessage() + loadAvatar() + + hasLoadedData = true + } + + // MARK: - Private + + private func loadDisplayName() { + roomProxy.displayName { [weak self] result in + guard let self = self else { return } + + switch result { + case .success(let displayName): + self.displayName = displayName + case .failure(let error): + MXLog.error("Failed fetching room display name with error: \(error)") + } + } + } + + private func loadLastMessage() { + if roomProxy.messages.last == nil { + roomProxy.paginateBackwards(count: 1) { [weak self] result in + guard let self = self else { return } + + switch result { + case .success: + self.lastMessage = self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last) + case .failure(let error): + MXLog.error("Failed back paginating with error: \(error)") + } + } + } + } + + private func loadAvatar() { + if let avatarURL = roomProxy.avatarURL { + mediaProvider.loadImageFromURL(avatarURL) { [weak self] result in + guard let self = self else { return } + + switch result { + case .success(let image): + self.avatar = image + case .failure(let error): + MXLog.error("Failed fetching room avatar with error: \(error)") + } + } + } + } +} diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProtocol.swift new file mode 100644 index 000000000..840d20b11 --- /dev/null +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProtocol.swift @@ -0,0 +1,32 @@ +// +// RoomSummaryProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 01/04/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import UIKit +import Combine + +enum RoomSummaryCallback { + case updatedData +} + +protocol RoomSummaryProtocol { + var id: String { get } + var name: String? { get } + var topic: String? { get } + var isDirect: Bool { get } + var isEncrypted: Bool { get } + var isSpace: Bool { get } + var isTombstoned: Bool { get } + + var displayName: String? { get } + var lastMessage: EventBrief? { get } + var avatar: UIImage? { get } + + var callbacks: PassthroughSubject { get } + + func loadData() +}