// // RoomTimelineController.swift // ElementX // // Created by Stefan Ceriu on 04.03.2022. // Copyright © 2022 Element. All rights reserved. // import Foundation import Combine import UIKit class RoomTimelineController: RoomTimelineControllerProtocol { private let timelineProvider: RoomTimelineProviderProtocol private let timelineItemFactory: RoomTimelineItemFactory private let mediaProvider: MediaProviderProtocol private let memberDetailsProvider: MemberDetailsProviderProtocol private var cancellables = Set() let callbacks = PassthroughSubject() private(set) var timelineItems = [RoomTimelineItemProtocol]() init(timelineProvider: RoomTimelineProviderProtocol, timelineItemFactory: RoomTimelineItemFactory, mediaProvider: MediaProviderProtocol, memberDetailsProvider: MemberDetailsProviderProtocol) { self.timelineProvider = timelineProvider self.timelineItemFactory = timelineItemFactory self.mediaProvider = mediaProvider self.memberDetailsProvider = memberDetailsProvider self.timelineProvider.callbacks.sink { [weak self] callback in guard let self = self else { return } switch callback { case .updatedMessages: self.updateTimelineItems() } }.store(in: &cancellables) updateTimelineItems() NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) } func paginateBackwards(_ count: UInt, callback: @escaping ((Result) -> Void)) { timelineProvider.paginateBackwards(count) { [weak self] result in switch result { case .success: callback(.success(())) self?.updateTimelineItems() case .failure: callback(.failure(.generic)) } } } func processItemAppearance(_ itemId: String) { guard let timelineItem = self.timelineItems.filter({ $0.id == itemId}).first else { return } if let item = timelineItem as? EventBasedTimelineItemProtocol { loadUserAvatarForTimelineItem(item) loadUserDisplayNameForTimelineItem(item) } switch timelineItem { case let item as ImageRoomTimelineItem: loadImageForTimelineItem(item) default: break } } func processItemDisappearance(_ itemId: String) { } // MARK: - Private @objc private func contentSizeCategoryDidChange() { // Recompute all attributed strings on content size changes -> DynamicType support updateTimelineItems() } private func updateTimelineItems() { var newTimelineItems = [RoomTimelineItemProtocol]() var previousMessage: RoomMessageProtocol? for message in self.timelineProvider.messages { let areMessagesFromTheSameDay = self.haveSameDay(lhs: previousMessage, rhs: message) let shouldAddSectionHeader = !areMessagesFromTheSameDay if shouldAddSectionHeader { newTimelineItems.append(SeparatorRoomTimelineItem(id: message.originServerTs.ISO8601Format(), text: message.originServerTs.formatted(date: .complete, time: .omitted))) } let areMessagesFromTheSameSender = (previousMessage?.sender == message.sender) let shouldShowSenderDetails = !areMessagesFromTheSameSender || !areMessagesFromTheSameDay newTimelineItems.append(timelineItemFactory.buildTimelineItemFor(message, showSenderDetails: shouldShowSenderDetails)) previousMessage = message } self.timelineItems = newTimelineItems self.callbacks.send(.updatedTimelineItems) } private func haveSameDay(lhs: RoomMessageProtocol?, rhs: RoomMessageProtocol?) -> Bool { guard let lhs = lhs, let rhs = rhs else { return false } return Calendar.current.isDate(lhs.originServerTs, inSameDayAs: rhs.originServerTs) } private func loadImageForTimelineItem(_ timelineItem: ImageRoomTimelineItem) { if timelineItem.image != nil { return } guard let url = timelineItem.url else { return } mediaProvider.loadImageFromURL(url) { [weak self] result in guard let self = self else { return } if case let .success(image) = result { guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }), var item = self.timelineItems[index] as? ImageRoomTimelineItem else { return } item.image = image self.timelineItems[index] = item self.callbacks.send(.updatedTimelineItem(timelineItem.id)) } } } private func loadUserAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) { if timelineItem.shouldShowSenderDetails == false { return } memberDetailsProvider.avatarURLForUserId(timelineItem.senderId) { result in if case let .success(avatarURL) = result, let avatarURL = avatarURL { self.mediaProvider.loadImageFromURL(avatarURL) { result in if case let .success(image) = result { guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }), var item = self.timelineItems[index] as? EventBasedTimelineItemProtocol else { return } item.senderAvatar = image self.timelineItems[index] = item self.callbacks.send(.updatedTimelineItem(timelineItem.id)) } } } } } private func loadUserDisplayNameForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) { if timelineItem.shouldShowSenderDetails == false { return } memberDetailsProvider.displayNameForUserId(timelineItem.senderId) { result in if case let .success(displayName) = result, let displayName = displayName { guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }), var item = self.timelineItems[index] as? EventBasedTimelineItemProtocol else { return } item.senderDisplayName = displayName self.timelineItems[index] = item self.callbacks.send(.updatedTimelineItem(timelineItem.id)) } } } }