mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Add the empty state to SpaceScreen. (#4985)
* Rename PaginationState.timelineEndReached to PaginationState.endReached. * Rename PaginationState to TimelinePaginationState. Also renames PaginationStatus to PaginationState so that a TimelinePaginationState consists of the forward and backward pagination states. * Add the empty state to SpaceScreen. Only has 1 of the 2 buttons for now.
This commit is contained in:
@@ -26,7 +26,7 @@ class MockTimelineController: TimelineControllerProtocol {
|
||||
|
||||
let callbacks = PassthroughSubject<TimelineControllerCallback, Never>()
|
||||
|
||||
var paginationState: PaginationState = .initial {
|
||||
var paginationState: TimelinePaginationState = .initial {
|
||||
didSet {
|
||||
callbacks.send(.paginationState(paginationState))
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class MockTimelineController: TimelineControllerProtocol {
|
||||
|
||||
static var emptyMediaGallery: MockTimelineController {
|
||||
let mock = MockTimelineController(timelineKind: .media(.mediaFilesScreen))
|
||||
mock.paginationState = PaginationState(backward: .timelineEndReached, forward: .timelineEndReached)
|
||||
mock.paginationState = TimelinePaginationState(backward: .endReached, forward: .endReached)
|
||||
return mock
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class MockTimelineController: TimelineControllerProtocol {
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineControllerError> {
|
||||
paginateBackwardsCallCount += 1
|
||||
|
||||
paginationState = PaginationState(backward: .paginating, forward: .timelineEndReached)
|
||||
paginationState = TimelinePaginationState(backward: .paginating, forward: .endReached)
|
||||
|
||||
if client == nil {
|
||||
try? await simulateBackPagination()
|
||||
@@ -331,8 +331,8 @@ class MockTimelineController: TimelineControllerProtocol {
|
||||
/// Prepends the next chunk of items to the `timelineItems` array.
|
||||
private func simulateBackPagination() async throws {
|
||||
defer {
|
||||
paginationState = PaginationState(backward: backPaginationResponses.isEmpty ? .timelineEndReached : .idle,
|
||||
forward: .timelineEndReached)
|
||||
paginationState = TimelinePaginationState(backward: backPaginationResponses.isEmpty ? .endReached : .idle,
|
||||
forward: .endReached)
|
||||
}
|
||||
|
||||
guard !backPaginationResponses.isEmpty else { return }
|
||||
|
||||
@@ -30,7 +30,7 @@ class TimelineController: TimelineControllerProtocol {
|
||||
|
||||
private(set) var timelineItems = [RoomTimelineItemProtocol]()
|
||||
|
||||
private(set) var paginationState: PaginationState = .initial {
|
||||
private(set) var paginationState: TimelinePaginationState = .initial {
|
||||
didSet {
|
||||
callbacks.send(.paginationState(paginationState))
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class TimelineController: TimelineControllerProtocol {
|
||||
}
|
||||
|
||||
Task {
|
||||
paginationState = PaginationState(backward: .paginating, forward: .paginating)
|
||||
paginationState = TimelinePaginationState(backward: .paginating, forward: .paginating)
|
||||
|
||||
switch await focusOnEvent(initialFocussedEventID, timelineSize: 100) {
|
||||
case .success:
|
||||
@@ -396,7 +396,7 @@ class TimelineController: TimelineControllerProtocol {
|
||||
isSwitchingTimelines = true
|
||||
|
||||
// Inform the world that the initial items are loading from the store
|
||||
paginationState = PaginationState(backward: .paginating, forward: .paginating)
|
||||
paginationState = TimelinePaginationState(backward: .paginating, forward: .paginating)
|
||||
callbacks.send(.isLive(activeTimelineItemProvider.kind == .live))
|
||||
|
||||
updateTimelineItemsCancellable = Task { [weak self, activeTimelineItemProvider] in
|
||||
@@ -411,7 +411,7 @@ class TimelineController: TimelineControllerProtocol {
|
||||
}.asCancellable()
|
||||
}
|
||||
|
||||
private func updateTimelineItems(itemProxies: [TimelineItemProxy], paginationState: PaginationState) async {
|
||||
private func updateTimelineItems(itemProxies: [TimelineItemProxy], paginationState: TimelinePaginationState) async {
|
||||
let isNewTimeline = isSwitchingTimelines
|
||||
isSwitchingTimelines = false
|
||||
|
||||
@@ -462,14 +462,14 @@ class TimelineController: TimelineControllerProtocol {
|
||||
switch paginationState.backward {
|
||||
case .paginating:
|
||||
newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(position: .start), at: 0)
|
||||
case .idle, .timelineEndReached:
|
||||
case .idle, .endReached:
|
||||
break
|
||||
}
|
||||
|
||||
switch paginationState.forward {
|
||||
case .paginating:
|
||||
newTimelineItems.insert(PaginationIndicatorRoomTimelineItem(position: .end), at: newTimelineItems.count)
|
||||
case .idle, .timelineEndReached:
|
||||
case .idle, .endReached:
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import SwiftUI
|
||||
|
||||
enum TimelineControllerCallback {
|
||||
case updatedTimelineItems(timelineItems: [RoomTimelineItemProtocol], isSwitchingTimelines: Bool)
|
||||
case paginationState(PaginationState)
|
||||
case paginationState(TimelinePaginationState)
|
||||
case isLive(Bool)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ protocol TimelineControllerProtocol {
|
||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||
|
||||
/// The current pagination state, use only for setting up the intial state
|
||||
var paginationState: PaginationState { get }
|
||||
var paginationState: TimelinePaginationState { get }
|
||||
|
||||
var callbacks: PassthroughSubject<TimelineControllerCallback, Never> { get }
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
|
||||
private var roomTimelineObservationToken: TaskHandle?
|
||||
|
||||
private let paginationStateSubject = CurrentValueSubject<PaginationState, Never>(.initial)
|
||||
var paginationState: PaginationState {
|
||||
private let paginationStateSubject = CurrentValueSubject<TimelinePaginationState, Never>(.initial)
|
||||
var paginationState: TimelinePaginationState {
|
||||
paginationStateSubject.value
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> {
|
||||
var updatePublisher: AnyPublisher<([TimelineItemProxy], TimelinePaginationState), Never> {
|
||||
itemProxiesSubject
|
||||
.combineLatest(paginationStateSubject)
|
||||
.eraseToAnyPublisher()
|
||||
@@ -46,7 +46,7 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
roomTimelineObservationToken?.cancel()
|
||||
}
|
||||
|
||||
init(timeline: Timeline, kind: TimelineKind, paginationStatePublisher: AnyPublisher<PaginationState, Never>) {
|
||||
init(timeline: Timeline, kind: TimelineKind, paginationStatePublisher: AnyPublisher<TimelinePaginationState, Never>) {
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.timeline_item_provider", qos: .utility)
|
||||
itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>([])
|
||||
self.kind = kind
|
||||
|
||||
@@ -10,22 +10,12 @@ import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum PaginationDirection: String {
|
||||
case backwards, forwards
|
||||
}
|
||||
|
||||
enum PaginationStatus {
|
||||
case idle
|
||||
case timelineEndReached
|
||||
case paginating
|
||||
}
|
||||
|
||||
struct PaginationState: Equatable {
|
||||
struct TimelinePaginationState: Equatable {
|
||||
/// An initial state that is used to prevent pagination whilst loading the timeline.
|
||||
/// Once the initial items are loaded the TimelineProxy will publish the correct value.
|
||||
static var initial = PaginationState(backward: .timelineEndReached, forward: .timelineEndReached)
|
||||
let backward: PaginationStatus
|
||||
let forward: PaginationStatus
|
||||
static var initial = TimelinePaginationState(backward: .endReached, forward: .endReached)
|
||||
let backward: PaginationState
|
||||
let forward: PaginationState
|
||||
}
|
||||
|
||||
/// Entities implementing this protocol are responsible for processings diffs coming from the rust timeline
|
||||
@@ -34,11 +24,11 @@ struct PaginationState: Equatable {
|
||||
@MainActor
|
||||
protocol TimelineItemProviderProtocol {
|
||||
/// A publisher that signals when ``itemProxies`` or ``paginationState`` are changed.
|
||||
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> { get }
|
||||
var updatePublisher: AnyPublisher<([TimelineItemProxy], TimelinePaginationState), Never> { get }
|
||||
/// The current set of items in the timeline.
|
||||
var itemProxies: [TimelineItemProxy] { get }
|
||||
/// Whether the timeline is back/forward paginating or not (or has reached the start/end of the room).
|
||||
var paginationState: PaginationState { get }
|
||||
var paginationState: TimelinePaginationState { get }
|
||||
/// The kind of the timeline
|
||||
var kind: TimelineKind { get }
|
||||
/// A publisher that signals when changes to the room's membership have occurred through `/sync`.
|
||||
|
||||
@@ -13,15 +13,15 @@ import MatrixRustSDK
|
||||
final class TimelineProxy: TimelineProxyProtocol {
|
||||
private let timeline: Timeline
|
||||
|
||||
private var backPaginationStatusObservationToken: TaskHandle?
|
||||
private var backPaginationStateObservationToken: TaskHandle?
|
||||
|
||||
// The default values shouldn't matter here as they will be updated when calling subscribeToPagination
|
||||
// but empirically we randomly see the timeline start virtual item when we shouldn't.
|
||||
// We believe there's a race condition between the default values the status publisher
|
||||
// so we're going to default the backwards one to .idle. Worst case it's going to do
|
||||
// one extra back pagination.
|
||||
private let backPaginationStatusSubject = CurrentValueSubject<PaginationStatus, Never>(.idle)
|
||||
private let forwardPaginationStatusSubject = CurrentValueSubject<PaginationStatus, Never>(.timelineEndReached)
|
||||
private let backPaginationStateSubject = CurrentValueSubject<PaginationState, Never>(.idle)
|
||||
private let forwardPaginationStateSubject = CurrentValueSubject<PaginationState, Never>(.endReached)
|
||||
|
||||
private let kind: TimelineKind
|
||||
|
||||
@@ -31,7 +31,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
|
||||
deinit {
|
||||
backPaginationStatusObservationToken?.cancel()
|
||||
backPaginationStateObservationToken?.cancel()
|
||||
}
|
||||
|
||||
init(timeline: Timeline, kind: TimelineKind) {
|
||||
@@ -45,9 +45,9 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
let paginationStatePublisher = backPaginationStatusSubject
|
||||
.combineLatest(forwardPaginationStatusSubject)
|
||||
.map { PaginationState(backward: $0.0, forward: $0.1) }
|
||||
let paginationStatePublisher = backPaginationStateSubject
|
||||
.combineLatest(forwardPaginationStateSubject)
|
||||
.map { TimelinePaginationState(backward: $0.0, forward: $0.1) }
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
await subscribeToPagination()
|
||||
@@ -131,8 +131,8 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
/// Rust subscription isn't allowed on focussed/detached timelines.
|
||||
private func focussedPaginate(_ direction: PaginationDirection, requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
|
||||
let subject = switch direction {
|
||||
case .backwards: backPaginationStatusSubject
|
||||
case .forwards: forwardPaginationStatusSubject
|
||||
case .backwards: backPaginationStateSubject
|
||||
case .forwards: forwardPaginationStateSubject
|
||||
}
|
||||
|
||||
// This extra check is necessary as detached timelines don't support subscribing to pagination status.
|
||||
@@ -150,7 +150,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
MXLog.info("Finished paginating \(direction.rawValue)")
|
||||
|
||||
subject.send(timelineEndReached ? .timelineEndReached : .idle)
|
||||
subject.send(timelineEndReached ? .endReached : .idle)
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed paginating \(direction.rawValue) with error: \(error)")
|
||||
@@ -592,28 +592,28 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
|
||||
switch status {
|
||||
case .idle(let hitStartOfTimeline):
|
||||
backPaginationStatusSubject.send(hitStartOfTimeline ? .timelineEndReached : .idle)
|
||||
backPaginationStateSubject.send(hitStartOfTimeline ? .endReached : .idle)
|
||||
case .paginating:
|
||||
backPaginationStatusSubject.send(.paginating)
|
||||
backPaginationStateSubject.send(.paginating)
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
backPaginationStatusObservationToken = try await timeline.subscribeToBackPaginationStatus(listener: backPaginationListener)
|
||||
backPaginationStateObservationToken = try await timeline.subscribeToBackPaginationStatus(listener: backPaginationListener)
|
||||
} catch {
|
||||
MXLog.error("Failed to subscribe to back pagination status with error: \(error)")
|
||||
}
|
||||
forwardPaginationStatusSubject.send(.timelineEndReached)
|
||||
forwardPaginationStateSubject.send(.endReached)
|
||||
case .detached, .thread:
|
||||
// Detached timelines don't support observation, set the initial state ourself.
|
||||
backPaginationStatusSubject.send(.idle)
|
||||
forwardPaginationStatusSubject.send(.idle)
|
||||
backPaginationStateSubject.send(.idle)
|
||||
forwardPaginationStateSubject.send(.idle)
|
||||
case .media(let presentation):
|
||||
backPaginationStatusSubject.send(presentation == .pinnedEventsScreen ? .timelineEndReached : .idle)
|
||||
forwardPaginationStatusSubject.send(presentation == .roomScreenDetached ? .idle : .timelineEndReached)
|
||||
backPaginationStateSubject.send(presentation == .pinnedEventsScreen ? .endReached : .idle)
|
||||
forwardPaginationStateSubject.send(presentation == .roomScreenDetached ? .idle : .endReached)
|
||||
case .pinned:
|
||||
backPaginationStatusSubject.send(.timelineEndReached)
|
||||
forwardPaginationStatusSubject.send(.timelineEndReached)
|
||||
backPaginationStateSubject.send(.endReached)
|
||||
forwardPaginationStateSubject.send(.endReached)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user