diff --git a/Packages/Timeline/Sources/Timeline/PendingStatusesObserver.swift b/Packages/Timeline/Sources/Timeline/PendingStatusesObserver.swift index 864d8bff..1aff61db 100644 --- a/Packages/Timeline/Sources/Timeline/PendingStatusesObserver.swift +++ b/Packages/Timeline/Sources/Timeline/PendingStatusesObserver.swift @@ -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) + } + } +} diff --git a/Packages/Timeline/Sources/Timeline/TimelineView.swift b/Packages/Timeline/Sources/Timeline/TimelineView.swift index 3b81ded6..babe46d8 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineView.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineView.swift @@ -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 diff --git a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift index 757ba731..ab0d7b00 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift @@ -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) } }