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:
Doug
2026-01-22 12:37:34 +00:00
committed by GitHub
parent b4b9f6affa
commit cea365ac97
30 changed files with 164 additions and 94 deletions
+2
View File
@@ -3159,6 +3159,8 @@ internal enum L10n {
internal static var screenSpaceAnnouncementSubtitle: String { return L10n.tr("Localizable", "screen_space_announcement_subtitle") }
/// Introducing Spaces
internal static var screenSpaceAnnouncementTitle: String { return L10n.tr("Localizable", "screen_space_announcement_title") }
/// Add your first room
internal static var screenSpaceEmptyStateTitle: String { return L10n.tr("Localizable", "screen_space_empty_state_title") }
/// Spaces you have created or joined.
internal static var screenSpaceListDescription: String { return L10n.tr("Localizable", "screen_space_list_description") }
/// %1$@ • %2$@
@@ -17622,17 +17622,17 @@ class TimelineControllerFactoryMock: TimelineControllerFactoryProtocol, @uncheck
}
}
class TimelineItemProviderMock: TimelineItemProviderProtocol, @unchecked Sendable {
var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> {
var updatePublisher: AnyPublisher<([TimelineItemProxy], TimelinePaginationState), Never> {
get { return underlyingUpdatePublisher }
set(value) { underlyingUpdatePublisher = value }
}
var underlyingUpdatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never>!
var underlyingUpdatePublisher: AnyPublisher<([TimelineItemProxy], TimelinePaginationState), Never>!
var itemProxies: [TimelineItemProxy] = []
var paginationState: PaginationState {
var paginationState: TimelinePaginationState {
get { return underlyingPaginationState }
set(value) { underlyingPaginationState = value }
}
var underlyingPaginationState: PaginationState!
var underlyingPaginationState: TimelinePaginationState!
var kind: TimelineKind {
get { return underlyingKind }
set(value) { underlyingKind = value }
@@ -15,7 +15,7 @@ import MatrixRustSDKMocks
class AutoUpdatingTimelineItemProviderMock: TimelineItemProvider {
static var timelineListener: TimelineListener?
private let innerPaginationStatePublisher: PassthroughSubject<PaginationState, Never>
private let innerPaginationStatePublisher: PassthroughSubject<TimelinePaginationState, Never>
init() {
innerPaginationStatePublisher = .init()
@@ -30,7 +30,7 @@ extension TimelineProxyMock {
underlyingTimelineItemProvider = AutoUpdatingTimelineItemProviderMock()
} else {
let timelineItemProvider = TimelineItemProviderMock()
timelineItemProvider.paginationState = .init(backward: configuration.timelineStartReached ? .timelineEndReached : .idle, forward: .timelineEndReached)
timelineItemProvider.paginationState = .init(backward: configuration.timelineStartReached ? .endReached : .idle, forward: .endReached)
timelineItemProvider.underlyingMembershipChangePublisher = PassthroughSubject().eraseToAnyPublisher()
underlyingTimelineItemProvider = timelineItemProvider
}
+18
View File
@@ -0,0 +1,18 @@
//
// 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 Foundation
enum PaginationDirection: String {
case backwards, forwards
}
enum PaginationState {
case idle
case endReached
case paginating
}
@@ -33,12 +33,12 @@ class TimelineMediaPreviewDataSource: NSObject, QLPreviewControllerDataSource {
private var backwardPadding: Int
private var forwardPadding: Int
var paginationState: PaginationState
var paginationState: TimelinePaginationState
init(itemViewStates: [RoomTimelineItemViewState],
initialItem: EventBasedMessageTimelineItemProtocol,
initialPadding: Int = 100,
paginationState: PaginationState) {
paginationState: TimelinePaginationState) {
previewItems = itemViewStates.compactMap(TimelineMediaPreviewItem.Media.init)
self.initialItem = initialItem
@@ -125,9 +125,9 @@ class TimelineMediaPreviewDataSource: NSObject, QLPreviewControllerDataSource {
let arrayIndex = index - backwardPadding
if index < firstPreviewItemIndex {
return paginationState.backward == .timelineEndReached ? TimelineMediaPreviewItem.Loading.timelineStart : .paginatingBackwards
return paginationState.backward == .endReached ? TimelineMediaPreviewItem.Loading.timelineStart : .paginatingBackwards
} else if index > lastPreviewItemIndex {
return paginationState.forward == .timelineEndReached ? TimelineMediaPreviewItem.Loading.timelineEnd : .paginatingForwards
return paginationState.forward == .endReached ? TimelineMediaPreviewItem.Loading.timelineEnd : .paginatingForwards
} else {
return previewItems[arrayIndex]
}
@@ -123,7 +123,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
updateWithTimelineViewState(activeTimelineViewModel.context.viewState)
case .oldestItemDidAppear:
isOldestItemVisible = true
backPaginateIfNecessary(paginationStatus: activeTimelineViewModel.context.viewState.timelineState.paginationState.backward)
backPaginateIfNecessary(backPaginationState: activeTimelineViewModel.context.viewState.timelineState.paginationState.backward)
case .oldestItemDidDisappear:
isOldestItemVisible = false
case .tappedItem(let item):
@@ -205,12 +205,12 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
state.groups = newGroups
state.isBackPaginating = timelineViewState.timelineState.paginationState.backward == .paginating
state.shouldShowEmptyState = newGroups.isEmpty && timelineViewState.timelineState.paginationState.backward == .timelineEndReached
backPaginateIfNecessary(paginationStatus: timelineViewState.timelineState.paginationState.backward)
state.shouldShowEmptyState = newGroups.isEmpty && timelineViewState.timelineState.paginationState.backward == .endReached
backPaginateIfNecessary(backPaginationState: timelineViewState.timelineState.paginationState.backward)
}
private func backPaginateIfNecessary(paginationStatus: PaginationStatus) {
if paginationStatus == .idle, isOldestItemVisible {
private func backPaginateIfNecessary(backPaginationState: PaginationState) {
if backPaginationState == .idle, isOldestItemVisible {
activeTimelineViewModel.context.send(viewAction: .paginateBackwards)
}
}
@@ -75,7 +75,7 @@ class RoomPollsHistoryScreenViewModel: RoomPollsHistoryScreenViewModelType, Room
case .updatedTimelineItems:
self.updatePollsList(filter: state.bindings.filter)
case .paginationState(let paginationState):
let canBackPaginate = paginationState.backward != .timelineEndReached
let canBackPaginate = paginationState.backward != .endReached
if self.state.canBackPaginate != canBackPaginate {
self.state.canBackPaginate = canBackPaginate
}
@@ -25,7 +25,7 @@ struct SpaceScreenViewState: BindableState {
var permalink: URL?
var roomProxy: JoinedRoomProxyProtocol?
var isPaginating = false
var paginationState: PaginationState = .idle
var rooms: [SpaceServiceRoomProtocol]
var selectedSpaceRoomID: String?
var joiningRoomIDs: Set<String> = []
@@ -40,6 +40,10 @@ struct SpaceScreenViewState: BindableState {
var bindings = SpaceScreenViewStateBindings()
var shouldShowEmptyState: Bool {
rooms.isEmpty && paginationState == .endReached && canEditChildren
}
var visibleRooms: [SpaceServiceRoomProtocol] {
if editMode == .inactive {
rooms
@@ -57,13 +57,16 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
spaceRoomListProxy.paginationStatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] paginationState in
guard let self else { return }
switch paginationState {
case .idle(let endReached):
self?.state.isPaginating = false
guard !endReached else { return }
case .idle(endReached: false):
state.paginationState = .idle
Task { await spaceRoomListProxy.paginate() }
case .idle(endReached: true):
state.paginationState = .endReached
case .loading:
self?.state.isPaginating = true
state.paginationState = .paginating
}
}
.store(in: &cancellables)
@@ -22,7 +22,11 @@ struct SpaceScreen: View {
mediaProvider: context.mediaProvider)
}
rooms
if context.viewState.shouldShowEmptyState {
emptyState
} else {
rooms
}
}
}
.environment(\.editMode, .constant(context.viewState.editMode))
@@ -53,12 +57,32 @@ struct SpaceScreen: View {
}
}
if context.viewState.isPaginating {
if context.viewState.paginationState == .paginating {
ProgressView()
.padding()
}
}
var emptyState: some View {
VStack(spacing: 24) {
TitleAndIcon(title: L10n.screenSpaceEmptyStateTitle,
icon: \.room,
iconStyle: .defaultSolid)
.padding(.horizontal, 24)
VStack(spacing: 16) {
Button { context.send(viewAction: .addExistingRooms) } label: {
Label(L10n.actionAddExistingRooms, icon: \.plus)
}
.buttonStyle(.compound(.primary))
// @Velin92 Create Room button goes here.
}
.padding(.horizontal, 16)
}
.padding(.top, 40)
}
@ToolbarContentBuilder
var toolbar: some ToolbarContent {
if isEditModeActive {
@@ -150,6 +174,7 @@ struct SpaceScreen: View {
struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = makeViewModel()
static let managingViewModel = makeViewModel(isManagingRooms: true)
static let newSpaceViewModel = makeViewModel(isNewSpace: true)
static var previews: some View {
NavigationStack {
@@ -160,9 +185,19 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
SpaceScreen(context: managingViewModel.context)
}
.previewDisplayName("Managing")
NavigationStack {
SpaceScreen(context: newSpaceViewModel.context)
}
.previewDisplayName("New Space")
.snapshotPreferences(expect: newSpaceViewModel.context.observe(\.viewState.shouldShowEmptyState))
}
static func makeViewModel(isManagingRooms: Bool = false) -> SpaceScreenViewModel {
static func makeViewModel(isManagingRooms: Bool = false, isNewSpace: Bool = false) -> SpaceScreenViewModel {
let appSettings = AppSettings()
appSettings.spaceSettingsEnabled = true
appSettings.createSpaceEnabled = true
let spaceServiceRoom = SpaceServiceRoomMock(.init(id: "!eng-space:matrix.org",
name: "Engineering Team",
isSpace: true,
@@ -173,11 +208,11 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
canonicalAlias: "#engineering-team:element.io",
joinRule: .knockRestricted(rules: [.roomMembership(roomId: "")])))
let spaceRoomListProxy = SpaceRoomListProxyMock(.init(spaceServiceRoom: spaceServiceRoom,
initialSpaceRooms: .mockSpaceList))
initialSpaceRooms: isNewSpace ? [] : .mockSpaceList))
let clientProxy = ClientProxyMock(.init())
clientProxy.roomForIdentifierClosure = { _ in
.joined(JoinedRoomProxyMock(.init()))
.joined(JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)))
}
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
@@ -185,7 +220,7 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
spaceServiceProxy: SpaceServiceProxyMock(.init()),
selectedSpaceRoomPublisher: .init(nil),
userSession: userSession,
appSettings: AppSettings(),
appSettings: appSettings,
userIndicatorController: UserIndicatorControllerMock())
if isManagingRooms {
@@ -210,7 +210,7 @@ struct RoomMemberState {
/// Is also nice to have this as a wrapper for any state that is directly connected to the timeline.
struct TimelineState {
var isLive = true
var paginationState = PaginationState.initial
var paginationState = TimelinePaginationState.initial
/// The room is in the process of loading items from a new timeline (switching to/from a detached timeline).
var isSwitchingTimelines = false
@@ -109,7 +109,7 @@ class TimelineTableViewController: UIViewController {
}
/// The state of pagination (in both directions) of the current timeline.
var paginationState: PaginationState = .initial {
var paginationState: TimelinePaginationState = .initial {
didSet {
// Paginate again if the threshold hasn't been satisfied.
paginatePublisher.send(())
@@ -614,7 +614,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
break
}
if state.timelineState.paginationState.forward == .timelineEndReached {
if state.timelineState.paginationState.forward == .endReached {
focusLive()
}
@@ -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
}
@@ -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)
}
}
}