Isolate pending statuses observer for smoother scrolling

This commit is contained in:
Thomas Ricouard 2023-01-31 09:01:26 +01:00
parent 0f98337a13
commit 0695fd5733
3 changed files with 33 additions and 26 deletions

View file

@ -20,3 +20,27 @@ class PendingStatusesObserver: ObservableObject {
init() { }
}
struct PendingStatusesObserverView: View {
@ObservedObject var observer: PendingStatusesObserver
@State var proxy: ScrollViewProxy
var body: some View {
if observer.pendingStatusesCount > 0 {
HStack(spacing: 6) {
Spacer()
Button {
withAnimation {
proxy.scrollTo(observer.pendingStatuses.last, anchor: .bottom)
}
} label: {
Text("\(observer.pendingStatusesCount)")
}
.buttonStyle(.bordered)
.background(.thinMaterial)
.cornerRadius(8)
}
.padding(12)
}
}
}

View file

@ -19,8 +19,7 @@ public struct TimelineView: View {
@EnvironmentObject private var routerPath: RouterPath
@StateObject private var viewModel = TimelineViewModel()
@StateObject private var pendingStatusesObserver = PendingStatusesObserver()
@State private var scrollProxy: ScrollViewProxy?
@Binding var timeline: TimelineFilter
@ -64,7 +63,6 @@ public struct TimelineView: View {
.navigationTitle(timeline.localizedTitle())
.navigationBarTitleDisplayMode(.inline)
.onAppear {
viewModel.pendingStatusesObserver = pendingStatusesObserver
if viewModel.client == nil {
viewModel.client = client
viewModel.timeline = timeline
@ -112,22 +110,7 @@ public struct TimelineView: View {
@ViewBuilder
private func makePendingNewPostsView(proxy: ScrollViewProxy) -> some View {
if pendingStatusesObserver.pendingStatusesCount > 0 {
HStack(spacing: 6) {
Spacer()
Button {
withAnimation {
proxy.scrollTo(pendingStatusesObserver.pendingStatuses.last, anchor: .bottom)
}
} label: {
Text("\(pendingStatusesObserver.pendingStatusesCount)")
}
.buttonStyle(.bordered)
.background(.thinMaterial)
.cornerRadius(8)
}
.padding(12)
}
PendingStatusesObserverView(observer: viewModel.pendingStatusesObserver, proxy: proxy)
}
@ViewBuilder

View file

@ -17,7 +17,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
// Internal source of truth for a timeline.
private var statuses: [Status] = []
var pendingStatusesObserver: PendingStatusesObserver?
var pendingStatusesObserver: PendingStatusesObserver = .init()
@Published var statusesState: StatusesState = .loading
@Published var timeline: TimelineFilter = .federated {
@ -25,7 +25,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
Task {
if oldValue != timeline {
statuses = []
pendingStatusesObserver?.pendingStatuses = []
pendingStatusesObserver.pendingStatuses = []
tag = nil
}
await fetchStatuses(userIntent: false)
@ -58,7 +58,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
guard let client else { return }
do {
if statuses.isEmpty {
pendingStatusesObserver?.pendingStatuses = []
pendingStatusesObserver.pendingStatuses = []
statusesState = .loading
statuses = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
maxId: nil,
@ -71,7 +71,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
var newStatuses: [Status] = await fetchNewPages(minId: first.id, maxPages: 20)
if userIntent || !pendingStatusesEnabled {
statuses.insert(contentsOf: newStatuses, at: 0)
pendingStatusesObserver?.pendingStatuses = []
pendingStatusesObserver.pendingStatuses = []
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage)
}
@ -79,7 +79,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
newStatuses = newStatuses.filter { status in
!statuses.contains(where: { $0.id == status.id })
}
pendingStatusesObserver?.pendingStatuses.insert(contentsOf: newStatuses.map{ $0.id }, at: 0)
pendingStatusesObserver.pendingStatuses.insert(contentsOf: newStatuses.map{ $0.id }, at: 0)
statuses.insert(contentsOf: newStatuses, at: 0)
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage)
@ -143,7 +143,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
pendingStatusesEnabled,
!statuses.contains(where: { $0.id == event.status.id })
{
pendingStatusesObserver?.pendingStatuses.insert(event.status.id, at: 0)
pendingStatusesObserver.pendingStatuses.insert(event.status.id, at: 0)
statuses.insert(event.status, at: 0)
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
@ -162,6 +162,6 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
}
func statusDidAppear(status: Status) {
pendingStatusesObserver?.removeStatus(status: status)
pendingStatusesObserver.removeStatus(status: status)
}
}