// // Copyright 2026 Element Creations Ltd. // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. // Please see LICENSE files in the repository root for full details. // import Combine import Foundation import MatrixRustSDK class RoomThreadListProxy: RoomThreadListServiceProxyProtocol { private let threadListService: ThreadListServiceProtocol private let eventStringBuilder: RoomEventStringBuilder // periphery:ignore - required for instance retention in the rust codebase private var itemUpdatesHandle: TaskHandle? private let itemsSubject = CurrentValueSubject<[RoomThreadListItem], Never>([]) var itemsPublisher: CurrentValuePublisher<[RoomThreadListItem], Never> { itemsSubject.asCurrentValuePublisher() } // periphery:ignore - required for instance retention in the rust codebase private var paginationStateHandle: TaskHandle? private let paginationStateSubject: CurrentValueSubject var paginationStatePublisher: CurrentValuePublisher { paginationStateSubject.asCurrentValuePublisher() } init(threadListService: ThreadListServiceProtocol, eventStringBuilder: RoomEventStringBuilder) { self.threadListService = threadListService self.eventStringBuilder = eventStringBuilder paginationStateSubject = CurrentValueSubject(.init(sdkState: threadListService.paginationState())) // The listeners are called by the SDK from arbitrary threads, hop to the // main actor where this proxy lives. paginationStateHandle = threadListService.subscribeToPaginationStateUpdates(listener: SDKListener { [weak self] state in Task { @MainActor in self?.paginationStateSubject.send(.init(sdkState: state)) } }) updateItems() itemUpdatesHandle = self.threadListService.subscribeToItemsUpdates(listener: SDKListener { [weak self] _ in Task { @MainActor in self?.updateItems() } }) } func paginate() async -> Result { do { try await threadListService.paginate() return .success(()) } catch { MXLog.error("Failed paginating with error: \(error)") return .failure(.sdkError(error)) } } // MARK: - Private private func updateItems() { let items = threadListService.items().map { item in guard let rootMessageDetails = buildMessageDetails(item.rootEvent) else { fatalError("This should never failâ„¢") } let latestMessageDetails = buildMessageDetails(item.latestEvent) return RoomThreadListItem(id: item.rootEvent.eventId, rootMessageDetails: rootMessageDetails, latestMessageDetails: latestMessageDetails, numberOfReplies: UInt(item.numReplies)) } itemsSubject.send(items) } private func buildMessageDetails(_ threadListItemEvent: ThreadListItemEvent?) -> RoomThreadListItem.MessageDetails? { guard let threadListItemEvent else { return nil } let sender = TimelineItemSender(senderID: threadListItemEvent.sender, senderProfile: threadListItemEvent.senderProfile) let timestamp = Date(timeIntervalSince1970: TimeInterval(threadListItemEvent.timestamp / 1000)) let message: AttributedString? = if let content = threadListItemEvent.content { eventStringBuilder.buildAttributedString(for: content, sender: sender, isOutgoing: threadListItemEvent.isOwn) } else { nil } return .init(sender: sender, timestamp: timestamp, message: message) } } extension RoomThreadListPaginationState { init(sdkState: MatrixRustSDK.ThreadListPaginationState) { switch sdkState { case .loading: self = .loading case .idle(let endReached): self = .idle(endReached: endReached) } } }