Timeline scroll to top UX / Flow
This commit is contained in:
parent
73fde0f6aa
commit
11d4d20873
|
@ -18,14 +18,22 @@ public struct TimelineView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
ScrollViewReader { proxy in
|
||||||
|
ZStack(alignment: .top) {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack {
|
LazyVStack {
|
||||||
tagHeaderView
|
tagHeaderView
|
||||||
.padding(.bottom, 16)
|
.padding(.bottom, 16)
|
||||||
|
.id("top")
|
||||||
StatusesListView(fetcher: viewModel)
|
StatusesListView(fetcher: viewModel)
|
||||||
}
|
}
|
||||||
.padding(.top, DS.Constants.layoutPadding)
|
.padding(.top, DS.Constants.layoutPadding)
|
||||||
}
|
}
|
||||||
|
if filter == .home {
|
||||||
|
makePendingNewPostsView(proxy: proxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.navigationTitle(filter?.title() ?? viewModel.timeline.title())
|
.navigationTitle(filter?.title() ?? viewModel.timeline.title())
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
@ -53,6 +61,22 @@ public struct TimelineView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func makePendingNewPostsView(proxy: ScrollViewProxy) -> some View {
|
||||||
|
if !viewModel.pendingStatuses.isEmpty {
|
||||||
|
Button {
|
||||||
|
proxy.scrollTo("top")
|
||||||
|
viewModel.displayPendingStatuses()
|
||||||
|
} label: {
|
||||||
|
Text("\(viewModel.pendingStatuses.count) new posts")
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
.background(.thinMaterial)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.padding(.top, 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var tagHeaderView: some View {
|
private var tagHeaderView: some View {
|
||||||
if let tag = viewModel.tag {
|
if let tag = viewModel.tag {
|
||||||
|
|
|
@ -27,6 +27,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var tag: Tag?
|
@Published var tag: Tag?
|
||||||
|
@Published var pendingStatuses: [Status] = []
|
||||||
|
|
||||||
var serverName: String {
|
var serverName: String {
|
||||||
client?.server ?? "Error"
|
client?.server ?? "Error"
|
||||||
|
@ -36,6 +37,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
if statuses.isEmpty {
|
if statuses.isEmpty {
|
||||||
|
pendingStatuses = []
|
||||||
statusesState = .loading
|
statusesState = .loading
|
||||||
statuses = try await client.get(endpoint: timeline.endpoint(sinceId: nil, maxId: nil))
|
statuses = try await client.get(endpoint: timeline.endpoint(sinceId: nil, maxId: nil))
|
||||||
} else if let first = statuses.first {
|
} else if let first = statuses.first {
|
||||||
|
@ -86,11 +88,16 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
|
||||||
func handleEvent(event: any StreamEvent) {
|
func handleEvent(event: any StreamEvent) {
|
||||||
guard timeline == .home else { return }
|
guard timeline == .home else { return }
|
||||||
if let event = event as? StreamEventUpdate {
|
if let event = event as? StreamEventUpdate {
|
||||||
statuses.insert(event.status, at: 0)
|
pendingStatuses.insert(event.status, at: 0)
|
||||||
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
|
||||||
} else if let event = event as? StreamEventDelete {
|
} else if let event = event as? StreamEventDelete {
|
||||||
statuses.removeAll(where: { $0.id == event.status })
|
statuses.removeAll(where: { $0.id == event.status })
|
||||||
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayPendingStatuses() {
|
||||||
|
statuses.insert(contentsOf: pendingStatuses, at: 0)
|
||||||
|
pendingStatuses = []
|
||||||
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue