diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/AppCoordinator.swift index 4b4e8f51c..76ca4bbd1 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/AppCoordinator.swift @@ -101,39 +101,31 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { fatalError("User session should be already setup at this point") } - showLoadingIndicator() + guard let roomProxy = userSession.rooms.first(where: { $0.id == roomIdentifier }) else { + MXLog.error("Invalid room identifier: \(roomIdentifier)") + return + } - userSession.getRoomList { [weak self] rooms in + let memberDetailsProvider = MemberDetailsProvider(roomProxy: roomProxy) + + let timelineItemFactory = RoomTimelineItemFactory(mediaProvider: userSession.mediaProvider, + memberDetailsProvider: memberDetailsProvider, + attributedStringBuilder: AttributedStringBuilder()) + + let timelineController = RoomTimelineController(timelineProvider: RoomTimelineProvider(roomProxy: roomProxy), + timelineItemFactory: timelineItemFactory, + mediaProvider: userSession.mediaProvider, + memberDetailsProvider: memberDetailsProvider) + + let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController, + roomName: roomProxy.name) + let coordinator = RoomScreenCoordinator(parameters: parameters) + + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator) { [weak self] in guard let self = self else { return } - self.hideLoadingIndicator() - - guard let roomProxy = rooms.filter({ $0.id == roomIdentifier}).first else { - MXLog.error("Invalid room identifier: \(roomIdentifier)") - return - } - - let memberDetailsProvider = MemberDetailsProvider(roomProxy: roomProxy) - - let timelineItemFactory = RoomTimelineItemFactory(mediaProvider: userSession.mediaProvider, - memberDetailsProvider: memberDetailsProvider, - attributedStringBuilder: AttributedStringBuilder()) - - let timelineController = RoomTimelineController(timelineProvider: RoomTimelineProvider(roomProxy: roomProxy), - timelineItemFactory: timelineItemFactory, - mediaProvider: userSession.mediaProvider, - memberDetailsProvider: memberDetailsProvider) - - let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController, - roomName: roomProxy.name) - let coordinator = RoomScreenCoordinator(parameters: parameters) - - self.add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator) { [weak self] in - guard let self = self else { return } - - self.remove(childCoordinator: coordinator) - } + self.remove(childCoordinator: coordinator) } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index 384d50db3..7bb92d8b2 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -91,8 +91,6 @@ final class HomeScreenCoordinator: Coordinator, Presentable { // MARK: - Private func updateRoomsList() { - parameters.userSession.getRoomList { [weak self] rooms in - self?.viewModel.updateWithRoomList(rooms) - } + self.viewModel.updateWithRoomList(parameters.userSession.rooms) } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index fe77a41f1..3722ff303 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -67,7 +67,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol self.roomList = roomList state.rooms = roomList.map { roomProxy in - roomFromProxy(roomProxy) + buildRoomFromProxy(roomProxy) } roomUpdateListeners.removeAll() @@ -75,10 +75,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol roomList.forEach({ roomProxy in roomProxy.callbacks.sink { [weak self] callback in switch callback { - case .updatedLastMessage: + case .updatedMessages: self?.loadLastMessageForRoomWithIdentifier(roomProxy.id) - default: - break } } .store(in: &roomUpdateListeners) @@ -92,9 +90,19 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol // MARK: - Private private func loadRoomDataForIdentifier(_ roomIdentifier: String) { - loadAvatarForRoomWithIdentifier(roomIdentifier) - loadRoomDisplayNameForRoomWithIdentifier(roomIdentifier) - loadLastMessageForRoomWithIdentifier(roomIdentifier) + 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) { @@ -149,22 +157,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol return } - if let lastMessage = room.lastMessage { - self.updateLastMessage(lastMessage, forRoomWithIdentifier: roomIdentifier) - } else { - room.paginateBackwards(count: 1) { result in - switch result { - case .success(let messages): - guard let lastMessage = messages.last else { - return - } - - self.updateLastMessage(lastMessage.body, forRoomWithIdentifier: roomIdentifier) - default: - break - } - } + 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) { @@ -174,15 +172,18 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol self.state.rooms[index].lastMessage = lastMessage } + + private func buildRoomFromProxy(_ roomProxy: RoomProxyProtocol) -> HomeScreenRoom { + let avatar = mediaProvider.imageForURL(roomProxy.avatarURL) - private func roomFromProxy(_ roomProxy: RoomProxyProtocol) -> HomeScreenRoom { - HomeScreenRoom(id: roomProxy.id, - displayName: roomProxy.name, - topic: roomProxy.topic, - lastMessage: roomProxy.lastMessage, - isDirect: roomProxy.isDirect, - isEncrypted: roomProxy.isEncrypted, - isSpace: roomProxy.isSpace, - isTombstoned: roomProxy.isTombstoned) + 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/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 80938f522..d7cfff1f1 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -38,9 +38,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol self.timelineController = timelineController self.timelineViewFactory = timelineViewFactory - super.init(initialViewState: RoomScreenViewState(roomTitle: roomName ?? "💥")) - - buildTimelineViews() + super.init(initialViewState: RoomScreenViewState(roomTitle: roomName ?? "Unknown room 💥")) timelineController.callbacks.sink { [weak self] callback in guard let self = self else { return } @@ -57,6 +55,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol self.state.items[viewIndex] = timelineViewFactory.buildTimelineViewFor(timelineItem) } }.store(in: &cancellables) + + buildTimelineViews() } // MARK: - Public @@ -80,8 +80,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol // MARK: - Private private func buildTimelineViews() { - state.items = timelineController.timelineItems.map { item in + let stateItems = timelineController.timelineItems.map { item in timelineViewFactory.buildTimelineViewFor(item) } + + state.items = stateItems } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift index 5b15a13e5..84fc0ff38 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift @@ -30,7 +30,7 @@ struct EmoteRoomTimelineView: View { struct EmoteRoomTimelineView_Previews: PreviewProvider { static var previews: some View { - body + body.preferredColorScheme(.light) body.preferredColorScheme(.dark) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EventBasedTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EventBasedTimelineView.swift index 43105cc29..809aac3fc 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EventBasedTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EventBasedTimelineView.swift @@ -51,7 +51,7 @@ struct EventBasedTimelineView: View { struct EventBasedTimelineView_Previews: PreviewProvider { static var previews: some View { - body + body.preferredColorScheme(.light) body.preferredColorScheme(.dark) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift index 7c3bc217a..3051c7bf6 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift @@ -35,7 +35,7 @@ struct FormattedBodyText: View { struct FormattedBodyText_Previews: PreviewProvider { static var previews: some View { - body + body.preferredColorScheme(.light) body.preferredColorScheme(.dark) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift index c931e13b9..88fda27ae 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift @@ -35,7 +35,7 @@ struct ImageRoomTimelineView: View { struct ImageRoomTimelineView_Previews: PreviewProvider { static var previews: some View { - body + body.preferredColorScheme(.light) body.preferredColorScheme(.dark) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift index 2eaa9aea3..a828aeb42 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift @@ -30,7 +30,7 @@ struct NoticeRoomTimelineView: View { struct NoticeRoomTimelineView_Previews: PreviewProvider { static var previews: some View { - body + body.preferredColorScheme(.light) body.preferredColorScheme(.dark) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift index 3d8d1eb53..09737d5c2 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/PlaceholderAvatarImage.swift @@ -36,7 +36,7 @@ struct PlaceholderAvatarImage: View { struct PlaceholderAvatarImage_Previews: PreviewProvider { static var previews: some View { - body + body.preferredColorScheme(.light) body.preferredColorScheme(.dark) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/SeparatorRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/SeparatorRoomTimelineView.swift index 0c46bc01a..7aabc7601 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/SeparatorRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/SeparatorRoomTimelineView.swift @@ -44,7 +44,7 @@ struct LabelledDivider: View { struct SeparatorRoomTimelineView_Previews: PreviewProvider { static var previews: some View { - body + body.preferredColorScheme(.light) body.preferredColorScheme(.dark) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift index c9b4ccb1b..79c00d370 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift @@ -27,7 +27,7 @@ struct TextRoomTimelineView: View { struct TextRoomTimelineView_Previews: PreviewProvider { static var previews: some View { - body + body.preferredColorScheme(.light) body.preferredColorScheme(.dark) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift index 7ff37dc32..458e4ba91 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift @@ -59,9 +59,16 @@ struct TimelineView: View { tableViewObserver = TableViewObserver(tableView: tableView, topDetectionOffset: (tableView.bounds.size.height / 3.0)) + tableViewObserver.scrollToBottom() + // Check if there are enough items. Otherwise ask for more attemptBackPagination() } + .onAppear(perform: { + if timelineItems != context.viewState.items { + timelineItems = context.viewState.items + } + }) .onReceive(tableViewObserver.scrollViewDidReachTop, perform: { if context.viewState.isBackPaginating { return @@ -202,6 +209,20 @@ private class TableViewObserver: NSObject, UITableViewDelegate { return (scrollView.contentOffset.y + scrollView.adjustedContentInset.top) <= topDetectionOffset } + func scrollToBottom() { + guard let tableView = tableView, + tableView.numberOfSections > 0 else { + return + } + + let currentItemCount = tableView.numberOfRows(inSection: 0) + guard currentItemCount > 1 else { + return + } + + tableView.scrollToRow(at: .init(row: currentItemCount - 1, section: 0), at: .bottom, animated: false) + } + // MARK: - UIScrollViewDelegate func scrollViewDidScroll(_ scrollView: UIScrollView) { diff --git a/ElementX/Sources/Services/Authentication/UserSession.swift b/ElementX/Sources/Services/Authentication/UserSession.swift index 73643f075..34c3702cb 100644 --- a/ElementX/Sources/Services/Authentication/UserSession.swift +++ b/ElementX/Sources/Services/Authentication/UserSession.swift @@ -32,7 +32,9 @@ private class WeakUserSessionWrapper: ClientDelegate { class UserSession: ClientDelegate { private let client: Client - private var rooms: [RoomProxy] = [] { + private let processingQueue: DispatchQueue + + private(set) var rooms: [RoomProxy] = [] { didSet { self.callbacks.send(.updatedRoomsList) } @@ -48,10 +50,13 @@ class UserSession: ClientDelegate { init(client: Client) { self.client = client + self.processingQueue = DispatchQueue(label: "UserSessionProcessingQueue") self.mediaProvider = MediaProvider(client: client, imageCache: ImageCache.default) client.setDelegate(delegate: WeakUserSessionWrapper(userSession: self)) client.startSync() + + updateRooms() } var userIdentifier: String { @@ -81,33 +86,39 @@ class UserSession: ClientDelegate { } } - func getRoomList(_ completion: @escaping ([RoomProxyProtocol]) -> Void) { - fetchRoomList(completion) - } - // MARK: ClientDelegate func didReceiveSyncUpdate() { - fetchRoomList { [weak self] rooms in - guard let self = self else { return } - if self.rooms != rooms { - self.rooms = rooms - } - } - - client.setDelegate(delegate: nil) + updateRooms() } // MARK: Private - func fetchRoomList(_ completion: @escaping ([RoomProxy]) -> Void) { - DispatchQueue.global(qos: .background).async { - let rooms = self.client.rooms().map { - return RoomProxy(room: $0, messageFactory: RoomMessageFactory()) + func updateRooms() { + var currentRooms = self.rooms + self.processingQueue.async { [weak self] in + guard let self = self else { + return + } + + let sdkRooms = self.client.rooms() + let diff = sdkRooms.map({ $0.id()}).difference(from: currentRooms.map({ $0.id })) + + for change in diff { + switch change { + case .insert(_, let id, _): + guard let sdkRoom = sdkRooms.first(where: { $0.id() == id }) else { + MXLog.error("Failed retrieving sdk room with id: \(id)") + break + } + currentRooms.append(RoomProxy(room: sdkRoom, messageFactory: RoomMessageFactory())) + case .remove(_, let id, _): + currentRooms.removeAll { $0.id == id } + } } DispatchQueue.main.async { - completion(rooms) + self.rooms = currentRooms } } } diff --git a/ElementX/Sources/Services/Media/MediaProvider.swift b/ElementX/Sources/Services/Media/MediaProvider.swift index 59cd858cb..a5fc60005 100644 --- a/ElementX/Sources/Services/Media/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/MediaProvider.swift @@ -18,7 +18,7 @@ struct MediaProvider: MediaProviderProtocol { init(client: Client, imageCache: Kingfisher.ImageCache) { self.client = client self.imageCache = imageCache - self.processingQueue = DispatchQueue(label: "MediaProviderProcessingQueue") + self.processingQueue = DispatchQueue(label: "MediaProviderProcessingQueue", attributes: .concurrent) } func imageForURL(_ url: String?) -> UIImage? { diff --git a/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift b/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift index 747fe2007..26a7fc4ca 100644 --- a/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift +++ b/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift @@ -10,7 +10,6 @@ import Foundation class MemberDetailsProvider: MemberDetailsProviderProtocol { private let roomProxy: RoomProxyProtocol? - private let processingQueue = DispatchQueue(label: "MemberDetailsProviderProcessingQueue") private var memberAvatars = [String: String]() private var memberDisplayNames = [String: String]() @@ -31,25 +30,19 @@ class MemberDetailsProvider: MemberDetailsProviderProtocol { completion(.success(avatarURL)) } - processingQueue.async { - roomProxy.avatarURLForUserId(userId, completion: { [weak self] result in - guard let self = self else { - return - } - - switch result { - case .success(let avatarURL): - DispatchQueue.main.async { - self.memberAvatars[userId] = avatarURL - completion(.success(avatarURL)) - } - case .failure: - DispatchQueue.main.async { - completion(.failure(.failedRetrievingUserAvatarURL)) - } - } - }) - } + roomProxy.avatarURLForUserId(userId, completion: { [weak self] result in + guard let self = self else { + return + } + + switch result { + case .success(let avatarURL): + self.memberAvatars[userId] = avatarURL + completion(.success(avatarURL)) + case .failure: + completion(.failure(.failedRetrievingUserAvatarURL)) + } + }) } func displayNameForUserId(_ userId: String) -> String? { @@ -65,24 +58,18 @@ class MemberDetailsProvider: MemberDetailsProviderProtocol { completion(.success(avatarURL)) } - processingQueue.async { - roomProxy.displayNameForUserId(userId, completion: { [weak self] result in - guard let self = self else { - return - } - - switch result { - case .success(let displayName): - DispatchQueue.main.async { - self.memberDisplayNames[userId] = displayName - completion(.success(displayName)) - } - case .failure: - DispatchQueue.main.async { - completion(.failure(.failedRetrievingUserDisplayName)) - } - } - }) - } + roomProxy.displayNameForUserId(userId, completion: { [weak self] result in + guard let self = self else { + return + } + + switch result { + case .success(let displayName): + self.memberDisplayNames[userId] = displayName + completion(.success(displayName)) + case .failure: + completion(.failure(.failedRetrievingUserDisplayName)) + } + }) } } diff --git a/ElementX/Sources/Services/Room/MockRoomProxy.swift b/ElementX/Sources/Services/Room/MockRoomProxy.swift index 33ad15af7..e720754ed 100644 --- a/ElementX/Sources/Services/Room/MockRoomProxy.swift +++ b/ElementX/Sources/Services/Room/MockRoomProxy.swift @@ -15,7 +15,7 @@ struct MockRoomProxy: RoomProxyProtocol { let displayName: String let topic: String? = nil - let lastMessage: String? = "Last message" + let messages: [RoomMessageProtocol] = [] let avatarURL: String? = nil @@ -35,7 +35,7 @@ struct MockRoomProxy: RoomProxyProtocol { } - func paginateBackwards(count: UInt, callback: ((Result<[RoomMessageProtocol], RoomProxyError>) -> Void)?) { + func paginateBackwards(count: UInt, callback: ((Result) -> Void)?) { } diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index db32f7077..8f5cbeb73 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -27,22 +27,31 @@ private class WeakRoomProxyWrapper: RoomDelegate { } } -class RoomProxy: RoomProxyProtocol, Equatable { +class RoomProxy: RoomProxyProtocol { private let room: Room private let messageFactory: RoomMessageFactory - private let processingQueue: DispatchQueue + + private let generalProcessingQueue: DispatchQueue + private let messageProcessingQueue: DispatchQueue private var backwardStream: BackwardsStreamProtocol? let callbacks = PassthroughSubject() + private(set) var messages: [RoomMessageProtocol] + init(room: Room, messageFactory: RoomMessageFactory) { self.room = room self.messageFactory = messageFactory - processingQueue = DispatchQueue(label: "RoomProxyProcessingQueue") + generalProcessingQueue = DispatchQueue(label: "RoomProxyGeneralProcessingQueue") + messageProcessingQueue = DispatchQueue(label: "RoomProxyMessageProcessingQueue") + messages = [] - processingQueue.async { - self.backwardStream = room.startLiveEventListener() + messageProcessingQueue.async { + let backwardStream = room.startLiveEventListener() + DispatchQueue.main.async { + self.backwardStream = backwardStream + } } room.setDelegate(delegate: WeakRoomProxyWrapper(roomProxy: self)) @@ -80,22 +89,12 @@ class RoomProxy: RoomProxyProtocol, Equatable { room.isTombstoned() } - var lastMessage: String? { - didSet { - if lastMessage == oldValue { - return - } - - callbacks.send(.updatedLastMessage) - } - } - var avatarURL: String? { room.avatarUrl() } func avatarURLForUserId(_ userId: String, completion: @escaping (Result) -> Void) { - processingQueue.async { + generalProcessingQueue.async { do { let avatarURL = try self.room.memberAvatarUrl(userId: userId) @@ -111,10 +110,10 @@ class RoomProxy: RoomProxyProtocol, Equatable { } func displayNameForUserId(_ userId: String, completion: @escaping (Result) -> Void) { - processingQueue.async { + generalProcessingQueue.async { do { let displayName = try self.room.memberDisplayName(userId: userId) - + DispatchQueue.main.async { completion(.success(displayName)) } @@ -127,10 +126,10 @@ class RoomProxy: RoomProxyProtocol, Equatable { } func displayName(_ completion: @escaping (Result) -> Void) { - processingQueue.async { + generalProcessingQueue.async { do { let displayName = try self.room.displayName() - + DispatchQueue.main.async { completion(.success(displayName)) } @@ -142,8 +141,8 @@ class RoomProxy: RoomProxyProtocol, Equatable { } } - func paginateBackwards(count: UInt, callback: ((Result<[RoomMessageProtocol], RoomProxyError>) -> Void)?) { - processingQueue.async { + func paginateBackwards(count: UInt, callback: ((Result) -> Void)?) { + messageProcessingQueue.async { guard let backwardStream = self.backwardStream else { DispatchQueue.main.async { callback?(.failure(.backwardStreamNotAvailable)) @@ -153,29 +152,20 @@ class RoomProxy: RoomProxyProtocol, Equatable { let messages = backwardStream.paginateBackwards(count: UInt64(count)).map { message in self.messageFactory.buildRoomMessageFrom(message) - } + }.reversed() DispatchQueue.main.async { - callback?(.success(messages)) - if self.lastMessage == nil { - self.lastMessage = messages.last?.body ?? "" - } + self.messages.insert(contentsOf: messages, at: 0) + callback?(.success(())) } } } - // MARK: - Equatable - - static func == (lhs: RoomProxy, rhs: RoomProxy) -> Bool { - lhs.id == rhs.id - } - // MARK: - Private fileprivate func appendMessage(_ message: AnyMessage) { let message = self.messageFactory.buildRoomMessageFrom(message) - lastMessage = message.body - - callbacks.send(.addedMessage(message)) + messages.append(message) + callbacks.send(.updatedMessages) } } diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 3cb3c73a2..58c7bab0b 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -17,8 +17,7 @@ enum RoomProxyError: Error { } enum RoomProxyCallback { - case addedMessage(RoomMessageProtocol) - case updatedLastMessage + case updatedMessages } protocol RoomProxyProtocol { @@ -32,7 +31,7 @@ protocol RoomProxyProtocol { var name: String? { get } var topic: String? { get } - var lastMessage: String? { get } + var messages: [RoomMessageProtocol] { get } var avatarURL: String? { get } @@ -42,7 +41,7 @@ protocol RoomProxyProtocol { func displayNameForUserId(_ userId: String, completion: @escaping (Result) -> Void) - func paginateBackwards(count: UInt, callback: ((Result<[RoomMessageProtocol], RoomProxyError>) -> Void)?) + func paginateBackwards(count: UInt, callback: ((Result) -> Void)?) var callbacks: PassthroughSubject { get } } diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index 1e755fc36..eb4a34bf8 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -35,11 +35,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol { guard let self = self else { return } switch callback { - case .addedMessage: + case .updatedMessages: self.updateTimelineItems() } }.store(in: &cancellables) + updateTimelineItems() + NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) } diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift index 9f945f59a..416fdb07f 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift @@ -14,7 +14,6 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { private var cancellables = Set() let callbacks = PassthroughSubject() - private(set) var messages = [RoomMessageProtocol]() init(roomProxy: RoomProxyProtocol) { self.roomProxy = roomProxy @@ -23,24 +22,24 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { guard let self = self else { return } switch callback { - case .addedMessage(let message): - self.messages.append(message) - self.callbacks.send(.addedMessage) - case .updatedLastMessage: - break + case .updatedMessages: + self.callbacks.send(.updatedMessages) } }.store(in: &cancellables) } - func paginateBackwards(_ count: UInt, callback: ((Result<([RoomMessageProtocol]), RoomTimelineError>) -> Void)?) { + func paginateBackwards(_ count: UInt, callback: ((Result) -> Void)?) { self.roomProxy.paginateBackwards(count: count) { result in switch result { - case .success(let messages): - self.messages.insert(contentsOf: messages.reversed(), at: 0) - callback?(.success((self.messages))) + case .success: + callback?(.success(())) case .failure: callback?(.failure(.generic)) } } } + + var messages: [RoomMessageProtocol] { + roomProxy.messages + } } diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift index c4dbef939..92fd933ab 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProviderProtocol.swift @@ -10,7 +10,7 @@ import Foundation import Combine enum RoomTimelineCallback { - case addedMessage + case updatedMessages } enum RoomTimelineError: Error { @@ -22,5 +22,5 @@ protocol RoomTimelineProviderProtocol { var messages: [RoomMessageProtocol] { get } - func paginateBackwards(_ count: UInt, callback: ((Result<([RoomMessageProtocol]), RoomTimelineError>) -> Void)?) + func paginateBackwards(_ count: UInt, callback: ((Result) -> Void)?) }