Introduced the concepts of room summary and event brief.

This commit is contained in:
Stefan Ceriu
2022-04-01 14:07:33 +03:00
parent 77942b3dc0
commit 86d85f4d2c
12 changed files with 402 additions and 104 deletions
@@ -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<AnyCancellable>()
// 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)
}
}
@@ -25,9 +25,11 @@ typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState,
class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
private let mediaProvider: MediaProviderProtocol
private let attributedStringBuilder: AttributedStringBuilderProtocol
private var roomUpdateListeners = Set<AnyCancellable>()
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)
}
}
@@ -22,5 +22,5 @@ protocol HomeScreenViewModelProtocol {
var context: HomeScreenViewModelType.Context { get }
func updateWithRoomList(_ roomList: [RoomProxyProtocol])
func updateWithRoomList(_ roomList: [RoomSummaryProtocol])
}
@@ -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)