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