mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 00:49:44 +00:00
Prepare for event focus and forward pagination (#2745)
Neither are available yet.
This commit is contained in:
@@ -22,9 +22,9 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private let serialDispatchQueue: DispatchQueue
|
||||
|
||||
private let backPaginationStateSubject = CurrentValueSubject<BackPaginationStatus, Never>(.idle)
|
||||
var backPaginationState: BackPaginationStatus {
|
||||
backPaginationStateSubject.value
|
||||
private let paginationStateSubject = CurrentValueSubject<PaginationState, Never>(.default)
|
||||
var paginationState: PaginationState {
|
||||
paginationStateSubject.value
|
||||
}
|
||||
|
||||
private let itemProxiesSubject: CurrentValueSubject<[TimelineItemProxy], Never>
|
||||
@@ -32,13 +32,14 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
itemProxiesSubject.value
|
||||
}
|
||||
|
||||
var updatePublisher: AnyPublisher<Void, Never> {
|
||||
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> {
|
||||
itemProxiesSubject
|
||||
.combineLatest(backPaginationStateSubject)
|
||||
.map { _, _ in () }
|
||||
.combineLatest(paginationStateSubject)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
private(set) var isLive: Bool
|
||||
|
||||
private let membershipChangeSubject = PassthroughSubject<Void, Never>()
|
||||
var membershipChangePublisher: AnyPublisher<Void, Never> {
|
||||
membershipChangeSubject
|
||||
@@ -46,10 +47,12 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
}
|
||||
|
||||
init(currentItems: [TimelineItem],
|
||||
isLive: Bool,
|
||||
updatePublisher: AnyPublisher<[TimelineDiff], Never>,
|
||||
backPaginationStatePublisher: AnyPublisher<BackPaginationStatus, Never>) {
|
||||
paginationStatePublisher: AnyPublisher<PaginationState, Never>) {
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||
itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>(currentItems.map(TimelineItemProxy.init))
|
||||
self.isLive = isLive
|
||||
|
||||
// Manually call it here as the didSet doesn't work from constructors
|
||||
itemProxiesSubject.send(itemProxies)
|
||||
@@ -59,8 +62,10 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
.sink { [weak self] in self?.updateItemsWithDiffs($0) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
backPaginationStatePublisher
|
||||
.sink { [weak self] in self?.backPaginationStateSubject.send($0) }
|
||||
paginationStatePublisher
|
||||
.sink { [weak self] in
|
||||
self?.paginationStateSubject.send($0)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,18 +16,25 @@
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
import MatrixRustSDK
|
||||
|
||||
struct PaginationState: Equatable {
|
||||
static var `default` = PaginationState(backward: .idle, forward: .timelineStartReached)
|
||||
let backward: BackPaginationStatus
|
||||
let forward: BackPaginationStatus
|
||||
}
|
||||
|
||||
@MainActor
|
||||
// sourcery: AutoMockable
|
||||
protocol RoomTimelineProviderProtocol {
|
||||
/// A publisher that signals when ``itemProxies`` or ``backPaginationState`` are changed.
|
||||
var updatePublisher: AnyPublisher<Void, Never> { get }
|
||||
/// A publisher that signals when ``itemProxies`` or ``paginationState`` are changed.
|
||||
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> { get }
|
||||
/// The current set of items in the timeline.
|
||||
var itemProxies: [TimelineItemProxy] { get }
|
||||
/// Whether the timeline is back paginating or not (or has reached the start of the room).
|
||||
var backPaginationState: BackPaginationStatus { get }
|
||||
/// Whether the timeline is back/forward paginating or not (or has reached the start/end of the room).
|
||||
var paginationState: PaginationState { get }
|
||||
/// Whether or not the provider is for a live timeline.
|
||||
var isLive: Bool { get }
|
||||
/// A publisher that signals when changes to the room's membership have occurred through `/sync`.
|
||||
///
|
||||
/// This is temporary and will be replace by a subscription on the room itself.
|
||||
|
||||
+31
-8
@@ -36,6 +36,9 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
private var client: UITestsSignalling.Client?
|
||||
|
||||
init(listenForSignals: Bool = false) {
|
||||
callbacks.send(.paginationState(PaginationState(backward: .idle, forward: .timelineStartReached)))
|
||||
callbacks.send(.isLive(true))
|
||||
|
||||
guard listenForSignals else { return }
|
||||
|
||||
do {
|
||||
@@ -44,17 +47,35 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
fatalError("Failure setting up signalling: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
try? await simulateBackPagination()
|
||||
|
||||
private(set) var focusOnEventCallCount = 0
|
||||
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||
focusOnEventCallCount += 1
|
||||
callbacks.send(.isLive(false))
|
||||
return .success(())
|
||||
}
|
||||
|
||||
private(set) var focusLiveCallCount = 0
|
||||
func focusLive() {
|
||||
focusLiveCallCount += 1
|
||||
callbacks.send(.isLive(true))
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
callbacks.send(.canBackPaginate(false))
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||
callbacks.send(.paginationState(PaginationState(backward: .paginating, forward: .timelineStartReached)))
|
||||
|
||||
if client == nil {
|
||||
try? await simulateBackPagination()
|
||||
}
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func paginateForwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||
// try? await simulateForwardPagination()
|
||||
.success(())
|
||||
}
|
||||
|
||||
func sendReadReceipt(for itemID: TimelineItemIdentifier) async {
|
||||
guard let roomProxy, let eventID = itemID.eventID else { return }
|
||||
_ = await roomProxy.timeline.sendReadReceipt(for: eventID, type: .read)
|
||||
@@ -151,14 +172,16 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
/// Prepends the next chunk of items to the `timelineItems` array.
|
||||
private func simulateBackPagination() async throws {
|
||||
defer {
|
||||
callbacks.send(.paginationState(PaginationState(backward: backPaginationResponses.isEmpty ? .timelineStartReached : .idle,
|
||||
forward: .timelineStartReached)))
|
||||
}
|
||||
|
||||
guard !backPaginationResponses.isEmpty else { return }
|
||||
callbacks.send(.isBackPaginating(true))
|
||||
|
||||
let newItems = backPaginationResponses.removeFirst()
|
||||
timelineItems.insert(contentsOf: newItems, at: 0)
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
callbacks.send(.isBackPaginating(false))
|
||||
callbacks.send(.canBackPaginate(!backPaginationResponses.isEmpty))
|
||||
|
||||
try client?.send(.success)
|
||||
}
|
||||
|
||||
+92
-49
@@ -21,7 +21,7 @@ import UIKit
|
||||
|
||||
class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private let timelineProvider: RoomTimelineProviderProtocol
|
||||
private let liveTimelineProvider: RoomTimelineProviderProtocol
|
||||
private let timelineItemFactory: RoomTimelineItemFactoryProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let serialDispatchQueue: DispatchQueue
|
||||
@@ -30,6 +30,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||
|
||||
private var activeTimeline: TimelineProxyProtocol
|
||||
private var activeTimelineProvider: RoomTimelineProviderProtocol {
|
||||
didSet {
|
||||
configureActiveTimelineProvider(clearExistingItems: true)
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
||||
|
||||
var roomID: String {
|
||||
@@ -40,34 +47,39 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||
appSettings: AppSettings) {
|
||||
self.roomProxy = roomProxy
|
||||
timelineProvider = roomProxy.timeline.timelineProvider
|
||||
liveTimelineProvider = roomProxy.timeline.timelineProvider
|
||||
self.timelineItemFactory = timelineItemFactory
|
||||
self.appSettings = appSettings
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||
|
||||
timelineProvider
|
||||
.updatePublisher
|
||||
.receive(on: serialDispatchQueue)
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
|
||||
self.updateTimelineItems()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
// Inform the world that the initial items are loading from the store
|
||||
callbacks.send(.isBackPaginating(true))
|
||||
serialDispatchQueue.async {
|
||||
self.updateTimelineItems()
|
||||
self.callbacks.send(.isBackPaginating(false))
|
||||
}
|
||||
activeTimeline = roomProxy.timeline
|
||||
activeTimelineProvider = liveTimelineProvider
|
||||
configureActiveTimelineProvider()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||
switch await roomProxy.timelineFocusedOnEvent(eventID: eventID, numberOfEvents: timelineSize) {
|
||||
case .success(let timeline):
|
||||
await timeline.subscribeForUpdates()
|
||||
activeTimeline = timeline
|
||||
activeTimelineProvider = timeline.timelineProvider
|
||||
return .success(())
|
||||
case .failure:
|
||||
return .failure(.generic)
|
||||
}
|
||||
}
|
||||
|
||||
func focusLive() {
|
||||
activeTimeline = roomProxy.timeline
|
||||
activeTimelineProvider = liveTimelineProvider
|
||||
callbacks.send(.isLive(true))
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||
MXLog.info("Started back pagination request")
|
||||
switch await roomProxy.timeline.paginateBackwards(requestSize: requestSize) {
|
||||
switch await activeTimeline.paginateBackwards(requestSize: requestSize) {
|
||||
case .success:
|
||||
MXLog.info("Finished back pagination request")
|
||||
return .success(())
|
||||
@@ -77,14 +89,14 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
MXLog.info("Started back pagination request")
|
||||
switch await roomProxy.timeline.paginateBackwards(requestSize: requestSize, untilNumberOfItems: untilNumberOfItems) {
|
||||
func paginateForwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError> {
|
||||
MXLog.info("Started forward pagination request")
|
||||
switch await activeTimeline.paginateForwards(requestSize: requestSize) {
|
||||
case .success:
|
||||
MXLog.info("Finished back pagination request")
|
||||
MXLog.info("Finished forward pagination request")
|
||||
return .success(())
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed back pagination request with error: \(error)")
|
||||
MXLog.error("Failed forward pagination request with error: \(error)")
|
||||
return .failure(.generic)
|
||||
}
|
||||
}
|
||||
@@ -149,7 +161,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
switch await roomProxy.timeline.toggleReaction(reaction, to: eventID) {
|
||||
switch await activeTimeline.toggleReaction(reaction, to: eventID) {
|
||||
case .success:
|
||||
MXLog.info("Finished toggling reaction")
|
||||
case .failure(let error):
|
||||
@@ -169,10 +181,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
await cancelSending(itemID: itemID)
|
||||
await sendMessage(newMessage, html: html, intentionalMentions: intentionalMentions)
|
||||
} else if let eventID = itemID.eventID {
|
||||
switch await roomProxy.timeline.editMessage(newMessage,
|
||||
html: html,
|
||||
original: eventID,
|
||||
intentionalMentions: intentionalMentions) {
|
||||
switch await activeTimeline.editMessage(newMessage,
|
||||
html: html,
|
||||
original: eventID,
|
||||
intentionalMentions: intentionalMentions) {
|
||||
case .success:
|
||||
MXLog.info("Finished editing message")
|
||||
case .failure(let error):
|
||||
@@ -199,7 +211,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
// Handle this parallel to the timeline items so we're not forced
|
||||
// to bundle the Rust side objects within them
|
||||
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo {
|
||||
for timelineItemProxy in timelineProvider.itemProxies {
|
||||
for timelineItemProxy in activeTimelineProvider.itemProxies {
|
||||
switch timelineItemProxy {
|
||||
case .event(let item):
|
||||
if item.id == itemID {
|
||||
@@ -214,7 +226,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
func retryDecryption(for sessionID: String) async {
|
||||
await roomProxy.timeline.retryDecryption(for: sessionID)
|
||||
await activeTimeline.retryDecryption(for: sessionID)
|
||||
}
|
||||
|
||||
func retrySending(itemID: TimelineItemIdentifier) async {
|
||||
@@ -239,19 +251,46 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@objc private func contentSizeCategoryDidChange() {
|
||||
// Recompute all attributed strings on content size changes -> DynamicType support
|
||||
serialDispatchQueue.async {
|
||||
self.updateTimelineItems()
|
||||
/// The cancellable used to update the timeline items.
|
||||
private var updateTimelineItemsCancellable: AnyCancellable?
|
||||
|
||||
/// Configures the controller to listen to `activeTimeline` for events.
|
||||
/// - Parameter clearExistingItems: Whether or not to clear any existing items before loading the timeline's contents.
|
||||
private func configureActiveTimelineProvider(clearExistingItems: Bool = false) {
|
||||
updateTimelineItemsCancellable = activeTimelineProvider
|
||||
.updatePublisher
|
||||
.receive(on: serialDispatchQueue)
|
||||
.sink { [weak self] items, paginationState in
|
||||
self?.updateTimelineItems(itemProxies: items, paginationState: paginationState)
|
||||
}
|
||||
|
||||
// Inform the world that the initial items are loading from the store
|
||||
callbacks.send(.paginationState(.init(backward: .paginating, forward: .paginating)))
|
||||
callbacks.send(.isLive(activeTimelineProvider.isLive))
|
||||
|
||||
if clearExistingItems {
|
||||
// Transition through empty to prevent animations.
|
||||
timelineItems.removeAll()
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
}
|
||||
|
||||
serialDispatchQueue.async { [activeTimelineProvider] in
|
||||
self.updateTimelineItems(itemProxies: activeTimelineProvider.itemProxies, paginationState: activeTimelineProvider.paginationState)
|
||||
self.callbacks.send(.paginationState(.init(backward: .idle, forward: .idle)))
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTimelineItems() {
|
||||
@objc private func contentSizeCategoryDidChange() {
|
||||
// Recompute all attributed strings on content size changes -> DynamicType support
|
||||
serialDispatchQueue.async { [activeTimelineProvider] in
|
||||
self.updateTimelineItems(itemProxies: activeTimelineProvider.itemProxies, paginationState: activeTimelineProvider.paginationState)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTimelineItems(itemProxies: [TimelineItemProxy], paginationState: PaginationState) {
|
||||
var newTimelineItems = [RoomTimelineItemProtocol]()
|
||||
var canBackPaginate = !roomProxy.timeline.timelineStartReached
|
||||
var isBackPaginating = false
|
||||
|
||||
let collapsibleChunks = timelineProvider.itemProxies.groupBy { isItemCollapsible($0) }
|
||||
let collapsibleChunks = itemProxies.groupBy { isItemCollapsible($0) }
|
||||
|
||||
for (index, collapsibleChunk) in collapsibleChunks.enumerated() {
|
||||
let isLastItem = index == collapsibleChunks.indices.last
|
||||
@@ -281,27 +320,31 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
// Check if we need to add anything to the top of the timeline.
|
||||
switch timelineProvider.backPaginationState {
|
||||
switch paginationState.backward {
|
||||
case .timelineStartReached:
|
||||
if !roomProxy.isEncryptedOneToOneRoom {
|
||||
let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name)
|
||||
newTimelineItems.insert(timelineStart, at: 0)
|
||||
}
|
||||
canBackPaginate = false
|
||||
case .paginating:
|
||||
newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(), at: 0)
|
||||
isBackPaginating = true
|
||||
newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(position: .start), at: 0)
|
||||
case .idle:
|
||||
break
|
||||
}
|
||||
|
||||
switch paginationState.forward {
|
||||
case .paginating:
|
||||
newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(position: .end), at: newTimelineItems.count)
|
||||
case .idle, .timelineStartReached:
|
||||
break
|
||||
}
|
||||
|
||||
DispatchQueue.main.sync {
|
||||
timelineItems = newTimelineItems
|
||||
}
|
||||
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
callbacks.send(.canBackPaginate(canBackPaginate))
|
||||
callbacks.send(.isBackPaginating(isBackPaginating))
|
||||
callbacks.send(.paginationState(paginationState))
|
||||
}
|
||||
|
||||
private func buildTimelineItem(for itemProxy: TimelineItemProxy) -> RoomTimelineItemProtocol? {
|
||||
@@ -355,10 +398,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
switch timelineItem.replyDetails {
|
||||
case .notLoaded:
|
||||
roomProxy.timeline.fetchDetails(for: eventID)
|
||||
activeTimeline.fetchDetails(for: eventID)
|
||||
case .error:
|
||||
if refetchOnError {
|
||||
roomProxy.timeline.fetchDetails(for: eventID)
|
||||
activeTimeline.fetchDetails(for: eventID)
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -366,7 +409,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? {
|
||||
for itemProxy in roomProxy.timeline.timelineProvider.itemProxies {
|
||||
for itemProxy in activeTimelineProvider.itemProxies {
|
||||
switch itemProxy {
|
||||
case .event(let eventTimelineItemProxy):
|
||||
if eventTimelineItemProxy.id == itemID {
|
||||
|
||||
+6
-4
@@ -20,8 +20,8 @@ import UIKit
|
||||
|
||||
enum RoomTimelineControllerCallback {
|
||||
case updatedTimelineItems
|
||||
case canBackPaginate(Bool)
|
||||
case isBackPaginating(Bool)
|
||||
case paginationState(PaginationState)
|
||||
case isLive(Bool)
|
||||
}
|
||||
|
||||
enum RoomTimelineControllerAction {
|
||||
@@ -45,9 +45,11 @@ protocol RoomTimelineControllerProtocol {
|
||||
|
||||
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, RoomTimelineControllerError>
|
||||
func focusOnEvent(_ eventID: String, timelineSize: UInt16) async -> Result<Void, RoomTimelineControllerError>
|
||||
func focusLive()
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError>
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError>
|
||||
func paginateForwards(requestSize: UInt16) async -> Result<Void, RoomTimelineControllerError>
|
||||
|
||||
func sendReadReceipt(for itemID: TimelineItemIdentifier) async
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ enum TimelineItemProxy {
|
||||
|
||||
init(item: MatrixRustSDK.TimelineItem) {
|
||||
if let eventItem = item.asEvent() {
|
||||
self = .event(EventTimelineItemProxy(item: eventItem, id: item.uniqueId()))
|
||||
self = .event(EventTimelineItemProxy(item: eventItem, id: String(item.uniqueId())))
|
||||
} else if let virtualItem = item.asVirtual() {
|
||||
self = .virtual(virtualItem, timelineID: String(item.uniqueId()))
|
||||
} else {
|
||||
@@ -67,9 +67,9 @@ class EventTimelineItemProxy {
|
||||
let item: MatrixRustSDK.EventTimelineItem
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
init(item: MatrixRustSDK.EventTimelineItem, id: UInt64) {
|
||||
init(item: MatrixRustSDK.EventTimelineItem, id: String) {
|
||||
self.item = item
|
||||
self.id = TimelineItemIdentifier(timelineID: String(id), eventID: item.eventId(), transactionID: item.transactionId())
|
||||
self.id = TimelineItemIdentifier(timelineID: id, eventID: item.eventId(), transactionID: item.transactionId())
|
||||
}
|
||||
|
||||
lazy var deliveryStatus: TimelineItemDeliveryStatus? = {
|
||||
|
||||
+16
-1
@@ -17,5 +17,20 @@
|
||||
import Foundation
|
||||
|
||||
struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id = TimelineItemIdentifier(timelineID: "paginationIndicatorTimelineItemIdentifier")
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
enum Position {
|
||||
case start, end
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .start: "backwardPaginationIndicatorTimelineItemIdentifier"
|
||||
case .end: "forwardPaginationIndicatorTimelineItemIdentifier"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(position: Position) {
|
||||
id = TimelineItemIdentifier(timelineID: position.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,21 +25,22 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
private let messageSendingDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.message_sending", qos: .userInitiated)
|
||||
private let userInitiatedDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.user_initiated", qos: .userInitiated)
|
||||
|
||||
private var backPaginationStateObservationToken: TaskHandle?
|
||||
private var backPaginationStatusObservationToken: TaskHandle?
|
||||
private var roomTimelineObservationToken: TaskHandle?
|
||||
|
||||
// periphery:ignore - retaining purpose
|
||||
private var timelineListener: RoomTimelineListener?
|
||||
|
||||
private let backPaginationStateSubject = PassthroughSubject<BackPaginationStatus, Never>()
|
||||
|
||||
private let backPaginationStatusSubject = CurrentValueSubject<BackPaginationStatus, Never>(.idle)
|
||||
private let forwardPaginationStatusSubject = CurrentValueSubject<BackPaginationStatus, Never>(.timelineStartReached)
|
||||
private let timelineUpdatesSubject = PassthroughSubject<[TimelineDiff], Never>()
|
||||
|
||||
private(set) var timelineStartReached = false
|
||||
|
||||
private let actionsSubject = PassthroughSubject<TimelineProxyAction, Never>()
|
||||
var actions: AnyPublisher<TimelineProxyAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let isLive: Bool
|
||||
|
||||
private var innerTimelineProvider: RoomTimelineProviderProtocol!
|
||||
var timelineProvider: RoomTimelineProviderProtocol {
|
||||
@@ -47,12 +48,13 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
|
||||
deinit {
|
||||
backPaginationStateObservationToken?.cancel()
|
||||
backPaginationStatusObservationToken?.cancel()
|
||||
roomTimelineObservationToken?.cancel()
|
||||
}
|
||||
|
||||
init(timeline: Timeline) {
|
||||
init(timeline: Timeline, isLive: Bool) {
|
||||
self.timeline = timeline
|
||||
self.isLive = isLive
|
||||
}
|
||||
|
||||
func subscribeForUpdates() async {
|
||||
@@ -70,11 +72,17 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
let result = await timeline.addListener(listener: timelineListener)
|
||||
roomTimelineObservationToken = result.itemsStream
|
||||
|
||||
subscribeToBackpagination()
|
||||
let paginationStatePublisher = backPaginationStatusSubject
|
||||
.combineLatest(forwardPaginationStatusSubject)
|
||||
.map { PaginationState(backward: $0.0, forward: $0.1) }
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
subscribeToPagination()
|
||||
|
||||
innerTimelineProvider = await RoomTimelineProvider(currentItems: result.items,
|
||||
isLive: isLive,
|
||||
updatePublisher: timelineUpdatesSubject.eraseToAnyPublisher(),
|
||||
backPaginationStatePublisher: backPaginationStateSubject.eraseToAnyPublisher())
|
||||
paginationStatePublisher: paginationStatePublisher)
|
||||
}
|
||||
|
||||
func cancelSend(transactionID: String) async {
|
||||
@@ -138,38 +146,43 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Paginating backwards with requestSize: \(requestSize)")
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Paginating backwards")
|
||||
|
||||
do {
|
||||
try await Task.dispatch(on: .global()) {
|
||||
try self.timeline.paginateBackwards(opts: .simpleRequest(eventLimit: UInt16(requestSize), waitForToken: true))
|
||||
}
|
||||
|
||||
MXLog.info("Finished paginating backwards with requestSize: \(requestSize)")
|
||||
_ = try timeline.paginateBackwards(opts: .simpleRequest(eventLimit: requestSize, waitForToken: true))
|
||||
MXLog.info("Finished paginating backwards")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed paginating backwards with requestSize: \(requestSize) with error: \(error)")
|
||||
MXLog.error("Failed paginating backwards with error: \(error)")
|
||||
return .failure(.failedPaginatingBackwards)
|
||||
}
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Paginating backwards with requestSize: \(requestSize), untilNumberOfItems: \(untilNumberOfItems)")
|
||||
|
||||
do {
|
||||
try await Task.dispatch(on: .global()) {
|
||||
try self.timeline.paginateBackwards(opts: .untilNumItems(eventLimit: UInt16(requestSize), items: UInt16(untilNumberOfItems), waitForToken: true))
|
||||
}
|
||||
|
||||
MXLog.info("Finished paginating backwards with requestSize: \(requestSize), untilNumberOfItems: \(untilNumberOfItems)")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Finished paginating backwards with requestSize: \(requestSize), untilNumberOfItems: \(untilNumberOfItems) with error: \(error)")
|
||||
return .failure(.failedPaginatingBackwards)
|
||||
}
|
||||
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
|
||||
.failure(.failedPaginatingBackwards)
|
||||
// This extra check is necessary as forwards pagination status doesn't support subscribing.
|
||||
// We need it to make sure we send a valid status after a failure.
|
||||
// guard forwardPaginationStatusSubject.value == .idle else {
|
||||
// MXLog.error("Attempting to paginate forwards when already at the end.")
|
||||
// return .failure(.failedPaginatingBackwards)
|
||||
// }
|
||||
//
|
||||
// MXLog.info("Paginating forwards")
|
||||
// forwardPaginationStatusSubject.send(.paginating)
|
||||
//
|
||||
// do {
|
||||
// let timelineEndReached = try await timeline.paginateForwards(numEvents: requestSize)
|
||||
// MXLog.info("Finished paginating forwards")
|
||||
//
|
||||
// forwardPaginationStatusSubject.send(timelineEndReached ? .timelineEndReached : .idle)
|
||||
// return .success(())
|
||||
// } catch {
|
||||
// MXLog.error("Failed paginating forwards with error: \(error)")
|
||||
// forwardPaginationStatusSubject.send(.idle)
|
||||
// return .failure(.failedPaginatingBackwards)
|
||||
// }
|
||||
}
|
||||
|
||||
func retryDecryption(for sessionID: String) async {
|
||||
@@ -536,18 +549,18 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func subscribeToBackpagination() {
|
||||
let listener = RoomBackpaginationStatusListener { [weak self] status in
|
||||
if status == .timelineStartReached {
|
||||
self?.timelineStartReached = true
|
||||
}
|
||||
self?.backPaginationStateSubject.send(status)
|
||||
private func subscribeToPagination() {
|
||||
let backPaginationListener = RoomPaginationStatusListener { [weak self] status in
|
||||
self?.backPaginationStatusSubject.send(status)
|
||||
}
|
||||
do {
|
||||
backPaginationStateObservationToken = try timeline.subscribeToBackPaginationStatus(listener: listener)
|
||||
backPaginationStatusObservationToken = try timeline.subscribeToBackPaginationStatus(listener: backPaginationListener)
|
||||
} catch {
|
||||
MXLog.error("Failed to subscribe to back pagination state with error: \(error)")
|
||||
MXLog.error("Failed to subscribe to back pagination status with error: \(error)")
|
||||
}
|
||||
|
||||
// Forward pagination doesn't support observation, set the initial state ourself.
|
||||
forwardPaginationStatusSubject.send(isLive ? .timelineStartReached : .idle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +576,7 @@ private final class RoomTimelineListener: TimelineListener {
|
||||
}
|
||||
}
|
||||
|
||||
private final class RoomBackpaginationStatusListener: BackPaginationStatusListener {
|
||||
private final class RoomPaginationStatusListener: BackPaginationStatusListener {
|
||||
private let onUpdateClosure: (BackPaginationStatus) -> Void
|
||||
|
||||
init(_ onUpdateClosure: @escaping (BackPaginationStatus) -> Void) {
|
||||
|
||||
@@ -41,9 +41,9 @@ enum TimelineProxyAction {
|
||||
protocol TimelineProxyProtocol {
|
||||
var actions: AnyPublisher<TimelineProxyAction, Never> { get }
|
||||
|
||||
var timelineProvider: RoomTimelineProviderProtocol { get }
|
||||
var isLive: Bool { get }
|
||||
|
||||
var timelineStartReached: Bool { get }
|
||||
var timelineProvider: RoomTimelineProviderProtocol { get }
|
||||
|
||||
func subscribeForUpdates() async
|
||||
|
||||
@@ -64,9 +64,8 @@ protocol TimelineProxyProtocol {
|
||||
/// Retries sending a failed message given its transaction ID
|
||||
func retrySend(transactionID: String) async
|
||||
|
||||
func paginateBackwards(requestSize: UInt) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, TimelineProxyError>
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError>
|
||||
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func sendAudio(url: URL,
|
||||
audioInfo: AudioInfo,
|
||||
|
||||
Reference in New Issue
Block a user