192 lines
7.1 KiB
Swift
192 lines
7.1 KiB
Swift
//
|
|
// 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<AnyCancellable>()
|
|
|
|
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
|
|
|
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 .addedMessage:
|
|
self.updateTimelineItems()
|
|
}
|
|
}.store(in: &cancellables)
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
|
}
|
|
|
|
func paginateBackwards(_ count: UInt, callback: @escaping ((Result<Void, RoomTimelineControllerError>) -> 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: .long, 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))
|
|
}
|
|
}
|
|
}
|
|
}
|