Timeline: Add indicator when loading new posts
This commit is contained in:
parent
c43d1d0dda
commit
3229bf0cb5
|
@ -112,7 +112,7 @@ public struct AccountDetailView: View {
|
|||
group.addTask { await viewModel.fetchAccount() }
|
||||
group.addTask {
|
||||
if await viewModel.statuses.isEmpty {
|
||||
await viewModel.fetchNewestStatuses()
|
||||
await viewModel.fetchNewestStatuses(pullToRefresh: false)
|
||||
}
|
||||
}
|
||||
if !viewModel.isCurrentUser {
|
||||
|
@ -126,7 +126,7 @@ public struct AccountDetailView: View {
|
|||
SoundEffectManager.shared.playSound(.pull)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.3))
|
||||
await viewModel.fetchAccount()
|
||||
await viewModel.fetchNewestStatuses()
|
||||
await viewModel.fetchNewestStatuses(pullToRefresh: true)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(.refresh)
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ import SwiftUI
|
|||
case .statuses, .postsAndReplies, .media:
|
||||
tabTask?.cancel()
|
||||
tabTask = Task {
|
||||
await fetchNewestStatuses()
|
||||
await fetchNewestStatuses(pullToRefresh: false)
|
||||
}
|
||||
default:
|
||||
reloadTabState()
|
||||
|
@ -170,7 +170,7 @@ import SwiftUI
|
|||
self.familiarFollowers = familiarFollowers?.first?.accounts ?? []
|
||||
}
|
||||
|
||||
func fetchNewestStatuses() async {
|
||||
func fetchNewestStatuses(pullToRefresh: Bool) async {
|
||||
guard let client else { return }
|
||||
do {
|
||||
tabState = .statuses(statusesState: .loading)
|
||||
|
|
|
@ -30,12 +30,12 @@ public struct AccountStatusesListView: View {
|
|||
.navigationTitle(viewModel.mode.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.refreshable {
|
||||
await viewModel.fetchNewestStatuses()
|
||||
await viewModel.fetchNewestStatuses(pullToRefresh: true)
|
||||
}
|
||||
.task {
|
||||
guard !isLoaded else { return }
|
||||
viewModel.client = client
|
||||
await viewModel.fetchNewestStatuses()
|
||||
await viewModel.fetchNewestStatuses(pullToRefresh: false)
|
||||
isLoaded = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ public class AccountStatusesListViewModel: StatusesFetcher {
|
|||
self.mode = mode
|
||||
}
|
||||
|
||||
public func fetchNewestStatuses() async {
|
||||
public func fetchNewestStatuses(pullToRefresh: Bool) async {
|
||||
guard let client else { return }
|
||||
statusesState = .loading
|
||||
do {
|
||||
|
|
|
@ -16,7 +16,7 @@ public enum StatusesState {
|
|||
@MainActor
|
||||
public protocol StatusesFetcher {
|
||||
var statusesState: StatusesState { get }
|
||||
func fetchNewestStatuses() async
|
||||
func fetchNewestStatuses(pullToRefresh: Bool) async
|
||||
func fetchNextPage() async
|
||||
func statusDidAppear(status: Status)
|
||||
func statusDidDisappear(status: Status)
|
||||
|
|
|
@ -40,7 +40,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
|||
buttonTitle: "action.retry")
|
||||
{
|
||||
Task {
|
||||
await fetcher.fetchNewestStatuses()
|
||||
await fetcher.fetchNewestStatuses(pullToRefresh: false)
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
|
|
|
@ -11,6 +11,8 @@ import DesignSystem
|
|||
|
||||
var disableUpdate: Bool = false
|
||||
var scrollToIndex: ((Int) -> Void)?
|
||||
|
||||
var isLoadingNewStatuses: Bool = false
|
||||
|
||||
var pendingStatuses: [String] = [] {
|
||||
didSet {
|
||||
|
@ -36,17 +38,26 @@ struct TimelineUnreadStatusesView: View {
|
|||
@Environment(Theme.self) private var theme
|
||||
|
||||
var body: some View {
|
||||
if observer.pendingStatusesCount > 0 {
|
||||
if observer.pendingStatusesCount > 0 || observer.isLoadingNewStatuses {
|
||||
Button {
|
||||
observer.scrollToIndex?(observer.pendingStatusesCount)
|
||||
} label: {
|
||||
Text("\(observer.pendingStatusesCount)")
|
||||
.contentTransition(.numericText(value: Double(observer.pendingStatusesCount)))
|
||||
// Accessibility: this results in a frame with a size of at least 44x44 at regular font size
|
||||
.frame(minWidth: 16, minHeight: 16)
|
||||
.font(.footnote.monospacedDigit())
|
||||
.fontWeight(.bold)
|
||||
.foregroundStyle(theme.labelColor)
|
||||
HStack {
|
||||
if observer.isLoadingNewStatuses {
|
||||
ProgressView()
|
||||
.tint(theme.labelColor)
|
||||
.transition(.scale)
|
||||
}
|
||||
if observer.pendingStatusesCount > 0 {
|
||||
Text("\(observer.pendingStatusesCount)")
|
||||
.contentTransition(.numericText(value: Double(observer.pendingStatusesCount)))
|
||||
// Accessibility: this results in a frame with a size of at least 44x44 at regular font size
|
||||
.frame(minWidth: 16, minHeight: 16)
|
||||
.font(.footnote.monospacedDigit())
|
||||
.fontWeight(.bold)
|
||||
.foregroundStyle(theme.labelColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
.accessibilityLabel("accessibility.tabs.timeline.unread-posts.label-\(observer.pendingStatusesCount)")
|
||||
.accessibilityHint("accessibility.tabs.timeline.unread-posts.hint")
|
||||
|
|
|
@ -30,7 +30,7 @@ import SwiftUI
|
|||
return
|
||||
}
|
||||
|
||||
await fetchNewestStatuses()
|
||||
await fetchNewestStatuses(pullToRefresh: false)
|
||||
switch timeline {
|
||||
case let .hashtag(tag, _):
|
||||
await fetchTag(id: tag)
|
||||
|
@ -55,7 +55,13 @@ import SwiftUI
|
|||
@ObservationIgnored
|
||||
private var visibileStatuses: [Status] = []
|
||||
|
||||
private var canStreamEvents: Bool = true
|
||||
private var canStreamEvents: Bool = true {
|
||||
didSet {
|
||||
if canStreamEvents {
|
||||
pendingStatusesObserver.isLoadingNewStatuses = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ObservationIgnored
|
||||
var canFilterTimeline: Bool = true
|
||||
|
@ -186,7 +192,7 @@ extension TimelineViewModel: StatusesFetcher {
|
|||
if !timeline.supportNewestPagination || UserPreferences.shared.fastRefreshEnabled {
|
||||
await reset()
|
||||
}
|
||||
await fetchNewestStatuses()
|
||||
await fetchNewestStatuses(pullToRefresh: true)
|
||||
}
|
||||
|
||||
func refreshTimeline() {
|
||||
|
@ -195,7 +201,7 @@ extension TimelineViewModel: StatusesFetcher {
|
|||
if UserPreferences.shared.fastRefreshEnabled {
|
||||
await reset()
|
||||
}
|
||||
await fetchNewestStatuses()
|
||||
await fetchNewestStatuses(pullToRefresh: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,10 +224,10 @@ extension TimelineViewModel: StatusesFetcher {
|
|||
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||
}
|
||||
|
||||
await fetchNewestStatuses()
|
||||
await fetchNewestStatuses(pullToRefresh: false)
|
||||
}
|
||||
|
||||
func fetchNewestStatuses() async {
|
||||
func fetchNewestStatuses(pullToRefresh: Bool) async {
|
||||
guard let client else { return }
|
||||
do {
|
||||
if let marker {
|
||||
|
@ -229,6 +235,7 @@ extension TimelineViewModel: StatusesFetcher {
|
|||
} else if await datasource.isEmpty {
|
||||
try await fetchFirstPage(client: client)
|
||||
} else if let latest = await datasource.get().first, timeline.supportNewestPagination {
|
||||
pendingStatusesObserver.isLoadingNewStatuses = !pullToRefresh
|
||||
try await fetchNewPagesFrom(latestStatus: latest.id, client: client)
|
||||
}
|
||||
} catch {
|
||||
|
@ -269,7 +276,7 @@ extension TimelineViewModel: StatusesFetcher {
|
|||
}
|
||||
}
|
||||
// And then we fetch statuses again toget newest statuses from there.
|
||||
await fetchNewestStatuses()
|
||||
await fetchNewestStatuses(pullToRefresh: false)
|
||||
} else {
|
||||
var statuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
||||
maxId: nil,
|
||||
|
@ -370,6 +377,7 @@ extension TimelineViewModel: StatusesFetcher {
|
|||
!Task.isCancelled,
|
||||
let latest = await datasource.get().first
|
||||
{
|
||||
pendingStatusesObserver.isLoadingNewStatuses = true
|
||||
try await fetchNewPagesFrom(latestStatus: latest.id, client: client)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue