Fix back pagination (#432)

* Use pagination and start items for view state.

isBackPaginating and canBackPaginate are updated each time the timeline is rebuilt

* Update some timeline snapshots

The top section has gone, which has altered the layout slightly.
This commit is contained in:
Doug 2023-01-11 09:11:36 +00:00 committed by GitHub
parent 3d1266ca25
commit 08b333839a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 54 additions and 72 deletions

View File

@ -49,6 +49,7 @@ struct RoomScreenViewState: BindableState {
var roomTitle = ""
var roomAvatar: UIImage?
var items: [RoomTimelineViewProvider] = []
var canBackPaginate = true
var isBackPaginating = false
var showLoading = false
var bindings: RoomScreenViewStateBindings

View File

@ -61,10 +61,14 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
self.state.items[viewIndex] = timelineViewFactory.buildTimelineViewFor(timelineItem: timelineItem)
case .startedBackPaginating:
self.state.isBackPaginating = true
case .finishedBackPaginating:
self.state.isBackPaginating = false
case .canBackPaginate(let canBackPaginate):
if self.state.canBackPaginate != canBackPaginate {
self.state.canBackPaginate = canBackPaginate
}
case .isBackPaginating(let isBackPaginating):
if self.state.isBackPaginating != isBackPaginating {
self.state.isBackPaginating = isBackPaginating
}
}
}
.store(in: &cancellables)

View File

@ -61,6 +61,9 @@ class TimelineTableViewController: UIViewController {
}
}
/// Whether or not the timeline has more messages to back paginate.
var canBackPaginate = true
/// Whether or not the timeline is waiting for more messages to be added to the top.
var isBackPaginating = false {
didSet {
@ -334,7 +337,8 @@ class TimelineTableViewController: UIViewController {
///
/// Prefer not to call this directly, instead using ``paginateBackwardsPublisher`` to throttle requests.
private func paginateBackwardsIfNeeded() {
guard !isBackPaginating,
guard canBackPaginate,
!isBackPaginating,
!hasPendingUpdates,
tableView.contentOffset.y < tableView.visibleSize.height * 2.0
else { return }

View File

@ -59,6 +59,9 @@ struct TimelineView: UIViewControllerRepresentable {
if tableViewController.timelineItems != context.viewState.items {
tableViewController.timelineItems = context.viewState.items
}
if tableViewController.canBackPaginate != context.viewState.canBackPaginate {
tableViewController.canBackPaginate = context.viewState.canBackPaginate
}
if tableViewController.isBackPaginating != context.viewState.isBackPaginating {
tableViewController.isBackPaginating = context.viewState.isBackPaginating
}

View File

@ -19,8 +19,6 @@ import Combine
struct MockRoomTimelineProvider: RoomTimelineProviderProtocol {
var itemsPublisher = CurrentValueSubject<[TimelineItemProxy], Never>([])
var backPaginationPublisher = CurrentValueSubject<Bool, Never>(false)
private var itemProxies = [TimelineItemProxy]()
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineProviderError> {

View File

@ -32,15 +32,10 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
private var cancellables = Set<AnyCancellable>()
let itemsPublisher = CurrentValueSubject<[TimelineItemProxy], Never>([])
let backPaginationPublisher = CurrentValueSubject<Bool, Never>(false)
private var itemProxies: [TimelineItemProxy] {
didSet {
itemsPublisher.send(itemProxies)
if backPaginationPublisher.value == true {
backPaginationPublisher.send(false)
}
}
}
@ -69,9 +64,6 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
}
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineProviderError> {
// Set this back to false after actually updating the items or if failed
backPaginationPublisher.send(true)
MXLog.info("Started back pagination request")
switch await roomProxy.paginateBackwards(requestSize: requestSize, untilNumberOfItems: untilNumberOfItems) {
case .success:
@ -79,7 +71,6 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
return .success(())
case .failure(let error):
MXLog.error("Failed back pagination request with error: \(error)")
backPaginationPublisher.send(false)
if error == .noMoreMessagesToBackPaginate {
return .failure(.noMoreMessagesToBackPaginate)

View File

@ -29,8 +29,6 @@ enum RoomTimelineProviderError: Error {
protocol RoomTimelineProviderProtocol {
var itemsPublisher: CurrentValueSubject<[TimelineItemProxy], Never> { get }
var backPaginationPublisher: CurrentValueSubject<Bool, Never> { get }
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineProviderError>
func sendMessage(_ message: String, inReplyToItemId: String?) async -> Result<Void, RoomTimelineProviderError>

View File

@ -42,20 +42,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
}
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> {
callbacks.send(.startedBackPaginating)
guard !backPaginationResponses.isEmpty else {
callbacks.send(.finishedBackPaginating)
return .failure(.generic)
}
let newItems = backPaginationResponses.removeFirst()
try? await Task.sleep(for: backPaginationDelay)
timelineItems.insert(contentsOf: newItems, at: 0)
callbacks.send(.updatedTimelineItems)
callbacks.send(.finishedBackPaginating)
callbacks.send(.canBackPaginate(false))
return .success(())
}
@ -125,11 +112,12 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
/// Prepends the next chunk of items to the `timelineItems` array.
private func simulateBackPagination() async throws {
guard !backPaginationResponses.isEmpty else { return }
callbacks.send(.isBackPaginating(true))
let newItems = backPaginationResponses.removeFirst()
timelineItems.insert(contentsOf: newItems, at: 0)
callbacks.send(.updatedTimelineItems)
callbacks.send(.finishedBackPaginating)
callbacks.send(.isBackPaginating(false))
try? await connection?.send(.success)
}

View File

@ -61,18 +61,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
.store(in: &cancellables)
self.timelineProvider
.backPaginationPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
if value {
self?.callbacks.send(.startedBackPaginating)
} else {
self?.callbacks.send(.finishedBackPaginating)
}
}
.store(in: &cancellables)
updateTimelineItems()
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
@ -230,6 +218,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
// swiftlint:disable:next cyclomatic_complexity
private func asyncUpdateTimelineItems() async {
var newTimelineItems = [RoomTimelineItemProtocol]()
var canBackPaginate = true
var isBackPaginating = false
for (index, itemProxy) in timelineProvider.itemsPublisher.value.enumerated() {
if Task.isCancelled {
@ -262,8 +252,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
case .loadingIndicator:
newTimelineItems.append(PaginationIndicatorRoomTimelineItem())
isBackPaginating = true
case .timelineStart:
newTimelineItems.append(TimelineStartRoomTimelineItem(name: roomProxy.displayName ?? roomProxy.name))
canBackPaginate = false
}
default:
break
@ -277,6 +269,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
timelineItems = newTimelineItems
callbacks.send(.updatedTimelineItems)
callbacks.send(.canBackPaginate(canBackPaginate))
callbacks.send(.isBackPaginating(isBackPaginating))
}
private func computeGroupState(for itemProxy: TimelineItemProxy,

View File

@ -21,8 +21,8 @@ import UIKit
enum RoomTimelineControllerCallback {
case updatedTimelineItems
case updatedTimelineItem(_ itemId: String)
case startedBackPaginating
case finishedBackPaginating
case canBackPaginate(Bool)
case isBackPaginating(Bool)
}
enum RoomTimelineControllerAction {

View File

@ -58,7 +58,7 @@ targets:
- path: ../SupportingFiles
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/UI
- path: ../../ElementX/Sources/UITests/UITestScreenIdentifier.swift
- path: ../../ElementX/Sources/UITests/UITestSignalling.swift
- path: ../../ElementX/Sources/UITests/UITestsSignalling.swift
- path: ../../ElementX/Sources/Generated/Strings.swift
- path: ../../ElementX/Sources/Generated/Strings+Untranslated.swift
- path: ../../ElementX/Resources

View File

@ -0,0 +1 @@
Use pagination indicators and start of room timeline items to update the view's pagination state.