diff --git a/Packages/Account/Sources/Account/AccountDetailViewModel.swift b/Packages/Account/Sources/Account/AccountDetailViewModel.swift index 0d53e5af..7e157eda 100644 --- a/Packages/Account/Sources/Account/AccountDetailViewModel.swift +++ b/Packages/Account/Sources/Account/AccountDetailViewModel.swift @@ -247,7 +247,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher { } } - func statusDidAppear(status: Models.Status) async { + func statusDidAppear(status: Models.Status) { } } diff --git a/Packages/Status/Sources/Status/List/StatusesFetcher.swift b/Packages/Status/Sources/Status/List/StatusesFetcher.swift index 09a0fbca..45facbcc 100644 --- a/Packages/Status/Sources/Status/List/StatusesFetcher.swift +++ b/Packages/Status/Sources/Status/List/StatusesFetcher.swift @@ -16,5 +16,5 @@ public protocol StatusesFetcher: ObservableObject { var statusesState: StatusesState { get } func fetchStatuses() async func fetchNextPage() async - func statusDidAppear(status: Status) async + func statusDidAppear(status: Status) } diff --git a/Packages/Status/Sources/Status/List/StatusesListView.swift b/Packages/Status/Sources/Status/List/StatusesListView.swift index 0c018f09..cfaa3803 100644 --- a/Packages/Status/Sources/Status/List/StatusesListView.swift +++ b/Packages/Status/Sources/Status/List/StatusesListView.swift @@ -56,9 +56,7 @@ public struct StatusesListView: View where Fetcher: StatusesFetcher { bottom: 12, trailing: .layoutPadding)) .onAppear { - Task { - await fetcher.statusDidAppear(status: status) - } + fetcher.statusDidAppear(status: status) } if !isEmbdedInList { Divider() diff --git a/Packages/Timeline/Sources/Timeline/PendingStatusesObserver.swift b/Packages/Timeline/Sources/Timeline/PendingStatusesObserver.swift new file mode 100644 index 00000000..864d8bff --- /dev/null +++ b/Packages/Timeline/Sources/Timeline/PendingStatusesObserver.swift @@ -0,0 +1,22 @@ +import Foundation +import SwiftUI +import Models + +@MainActor +class PendingStatusesObserver: ObservableObject { + @Published var pendingStatusesCount: Int = 0 + + var pendingStatuses: [String] = [] { + didSet { + pendingStatusesCount = pendingStatuses.count + } + } + + func removeStatus(status: Status) { + if let index = pendingStatuses.firstIndex(of: status.id) { + pendingStatuses.removeSubrange(index...(pendingStatuses.count - 1)) + } + } + + init() { } +} diff --git a/Packages/Timeline/Sources/Timeline/TimelineView.swift b/Packages/Timeline/Sources/Timeline/TimelineView.swift index ee10da3e..3b81ded6 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineView.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineView.swift @@ -19,8 +19,10 @@ 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 @Binding var scrollToTopSignal: Int @@ -62,9 +64,14 @@ public struct TimelineView: View { .navigationTitle(timeline.localizedTitle()) .navigationBarTitleDisplayMode(.inline) .onAppear { + viewModel.pendingStatusesObserver = pendingStatusesObserver if viewModel.client == nil { viewModel.client = client viewModel.timeline = timeline + } else { + Task { + await viewModel.fetchStatuses(userIntent: false) + } } } .refreshable { @@ -105,15 +112,15 @@ public struct TimelineView: View { @ViewBuilder private func makePendingNewPostsView(proxy: ScrollViewProxy) -> some View { - if !viewModel.pendingStatuses.isEmpty { + if pendingStatusesObserver.pendingStatusesCount > 0 { HStack(spacing: 6) { Spacer() Button { withAnimation { - proxy.scrollTo(viewModel.pendingStatuses.last?.id, anchor: .bottom) + proxy.scrollTo(pendingStatusesObserver.pendingStatuses.last, anchor: .bottom) } } label: { - Text(viewModel.pendingStatusesButtonTitle) + Text("\(pendingStatusesObserver.pendingStatusesCount)") } .buttonStyle(.bordered) .background(.thinMaterial) diff --git a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift index d7513063..757ba731 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift @@ -16,6 +16,8 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { // Internal source of truth for a timeline. private var statuses: [Status] = [] + + var pendingStatusesObserver: PendingStatusesObserver? @Published var statusesState: StatusesState = .loading @Published var timeline: TimelineFilter = .federated { @@ -23,7 +25,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { Task { if oldValue != timeline { statuses = [] - pendingStatuses = [] + pendingStatusesObserver?.pendingStatuses = [] tag = nil } await fetchStatuses(userIntent: false) @@ -38,11 +40,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { } @Published var tag: Tag? - @Published var pendingStatuses: [Status] = [] - - var pendingStatusesButtonTitle: LocalizedStringKey { - "\(pendingStatuses.count)" - } + @Published var pendingStatusesCount: Int = 0 var pendingStatusesEnabled: Bool { timeline == .home @@ -60,7 +58,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { guard let client else { return } do { if statuses.isEmpty { - pendingStatuses = [] + pendingStatusesObserver?.pendingStatuses = [] statusesState = .loading statuses = try await client.get(endpoint: timeline.endpoint(sinceId: nil, maxId: nil, @@ -69,11 +67,11 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { withAnimation { statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage) } - } else if let first = pendingStatuses.first ?? statuses.first { + } else if let first = statuses.first { var newStatuses: [Status] = await fetchNewPages(minId: first.id, maxPages: 20) if userIntent || !pendingStatusesEnabled { - statuses.insert(contentsOf: pendingStatuses, at: 0) - pendingStatuses = [] + statuses.insert(contentsOf: newStatuses, at: 0) + pendingStatusesObserver?.pendingStatuses = [] withAnimation { statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage) } @@ -81,7 +79,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { newStatuses = newStatuses.filter { status in !statuses.contains(where: { $0.id == status.id }) } - pendingStatuses.insert(contentsOf: newStatuses, 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) @@ -145,7 +143,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { pendingStatusesEnabled, !statuses.contains(where: { $0.id == event.status.id }) { - pendingStatuses.insert(event.status, at: 0) + pendingStatusesObserver?.pendingStatuses.insert(event.status.id, at: 0) statuses.insert(event.status, at: 0) withAnimation { statusesState = .display(statuses: statuses, nextPageState: .hasNextPage) @@ -163,9 +161,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { } } - func statusDidAppear(status: Status) async { - if let index = pendingStatuses.firstIndex(of: status) { - pendingStatuses.remove(at: index) - } + func statusDidAppear(status: Status) { + pendingStatusesObserver?.removeStatus(status: status) } }