Fix older notifications not loading when initially visible set fits on one screen

Closes #346
This commit is contained in:
Shadowfacts 2023-10-19 21:21:50 -04:00
parent 53d43b5707
commit c4bf5d406d
2 changed files with 33 additions and 1 deletions

View File

@ -539,6 +539,10 @@ extension NotificationsCollectionViewController: UICollectionViewDelegate {
let itemsInSection = collectionView.numberOfItems(inSection: indexPath.section)
if indexPath.row == itemsInSection - 1 {
Task {
// Because of grouping, all cells from the first load may fit on screen,
// in which case, we try to load older while still in the loadingInitial state.
// So, wait for that to finish before trying to load more.
await controller.finishPendingOperation()
await controller.loadOlder()
}
}

View File

@ -8,6 +8,7 @@
import Foundation
import OSLog
import Combine
protocol TimelineLikeControllerDelegate<TimelineItem>: AnyObject {
associatedtype TimelineItem: Sendable
@ -42,7 +43,7 @@ class TimelineLikeController<Item: Sendable> {
private unowned var delegate: any TimelineLikeControllerDelegate<Item>
private let ownerType: String
private(set) var state = State.notLoadedInitial {
@AsyncObservable private(set) var state = State.notLoadedInitial {
willSet {
guard state.canTransition(to: newValue) else {
logger.error("\(self.ownerType, privacy: .public) State \(self.state.debugDescription, privacy: .public) cannot transition to \(newValue.debugDescription, privacy: .public)")
@ -57,6 +58,19 @@ class TimelineLikeController<Item: Sendable> {
self.ownerType = ownerType
}
/// Waits for the controller to finish the current operation and arrive at the idle state.
///
/// If the current state is `notLoadedInitial`, this will wait until the controller
/// settles after the initial load.
func finishPendingOperation() async {
guard state != .idle else {
return
}
for await state in $state where state == .idle {
break
}
}
func loadInitial() async {
guard state == .notLoadedInitial || state == .idle else {
return
@ -369,3 +383,17 @@ enum TimelineGapDirection {
}
}
}
// I would love to be able to do this with @Observable, but it's not clear how to do so.
@propertyWrapper
private class AsyncObservable<Value>: ObservableObject {
@Published var wrappedValue: Value
var projectedValue: AsyncPublisher<Published<Value>.Publisher> {
$wrappedValue.values
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}