Skip to content

Commit

Permalink
Cleanup + use ScrollPoxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Dimillian committed Sep 17, 2024
1 parent 576b52e commit 9ec6a4e
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 103 deletions.
43 changes: 0 additions & 43 deletions Packages/Timeline/Sources/Timeline/TimelineMediaPrefetcher.swift

This file was deleted.

45 changes: 14 additions & 31 deletions Packages/Timeline/Sources/Timeline/View/TimelineView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Network
import StatusKit
import SwiftData
import SwiftUI
import SwiftUIIntrospect

@MainActor
public struct TimelineView: View {
Expand All @@ -20,11 +19,9 @@ public struct TimelineView: View {
@Environment(RouterPath.self) private var routerPath

@State private var viewModel = TimelineViewModel()
@State private var prefetcher = TimelineMediaPrefetcher()
@State private var contentFilter = TimelineContentFilter.shared

@State private var wasBackgrounded: Bool = false
@State private var collectionView: UICollectionView?

@Binding var timeline: TimelineFilter
@Binding var pinnedFilters: [TimelineFilter]
Expand Down Expand Up @@ -67,18 +64,6 @@ public struct TimelineView: View {
.if(canFilterTimeline && !pinnedFilters.isEmpty) { view in
view.toolbarBackground(.hidden, for: .navigationBar)
}
.onChange(of: viewModel.scrollToIndex) { _, newValue in
if let collectionView,
let newValue,
let rows = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: 0),
rows > newValue
{
collectionView.scrollToItem(at: .init(row: newValue, section: 0),
at: .top,
animated: false)
viewModel.scrollToIndex = nil
}
}
.toolbar {
toolbarTitleView
toolbarTagGroupButton
Expand Down Expand Up @@ -193,25 +178,23 @@ public struct TimelineView: View {
.id(client.id)
.environment(\.defaultMinListRowHeight, 1)
.listStyle(.plain)
#if !os(visionOS)
.scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor)
#endif
.introspect(.list, on: .iOS(.v17, .v18)) { (collectionView: UICollectionView) in
DispatchQueue.main.async {
self.collectionView = collectionView
}
prefetcher.viewModel = viewModel
collectionView.isPrefetchingEnabled = true
collectionView.prefetchDataSource = prefetcher
#if !os(visionOS)
.scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor)
#endif
.onChange(of: viewModel.scrollToId) { _, newValue in
if let newValue {
proxy.scrollTo(newValue, anchor: .top)
viewModel.scrollToId = nil
}
.onChange(of: selectedTabScrollToTop) { _, newValue in
if newValue == 0, routerPath.path.isEmpty {
withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
}
}
.onChange(of: selectedTabScrollToTop) { _, newValue in
if newValue == 0, routerPath.path.isEmpty {
withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
}
}
}
}
}

Expand Down
41 changes: 12 additions & 29 deletions Packages/Timeline/Sources/Timeline/View/TimelineViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import SwiftUI

@MainActor
@Observable class TimelineViewModel {
var scrollToIndex: Int?
var scrollToId: String?
var statusesState: StatusesState = .loading
var timeline: TimelineFilter = .home {
willSet {
Expand Down Expand Up @@ -229,13 +229,11 @@ extension TimelineViewModel: StatusesFetcher {
{
await datasource.set(cachedStatuses)
let statuses = await datasource.getFiltered()
if let latestSeenId = await cache.getLatestSeenStatus(for: client, filter: timeline.id)?.first,
let index = await datasource.indexOf(statusId: latestSeenId),
index > 0
if let latestSeenId = await cache.getLatestSeenStatus(for: client, filter: timeline.id)?.first
{
// Restore cache and scroll to latest seen status.
scrollToId = latestSeenId
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
scrollToIndex = index + 1
} else {
// Restore cache and scroll to top.
withAnimation {
Expand Down Expand Up @@ -299,6 +297,9 @@ extension TimelineViewModel: StatusesFetcher {
}

private func updateTimelineWithNewStatuses(_ newStatuses: [Status]) async {
defer {
canStreamEvents = true
}
let topStatus = await datasource.getFiltered().first
await datasource.insert(contentOf: newStatuses, at: 0)
if let lastVisible = visibleStatuses.last {
Expand All @@ -313,33 +314,15 @@ extension TimelineViewModel: StatusesFetcher {
visibleStatuses.contains(where: { $0.id == topStatus.id }),
scrollToTopVisible
{
updateTimelineWithScrollToTop(newStatuses: newStatuses, statuses: statuses, nextPageState: .hasNextPage)
scrollToId = topStatus.id
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
} else {
updateTimelineWithAnimation(statuses: statuses, nextPageState: .hasNextPage)
}
}

// Refresh the timeline while keeping the scroll position to the top status.
private func updateTimelineWithScrollToTop(newStatuses: [Status], statuses: [Status], nextPageState: StatusesState.PagingState) {
pendingStatusesObserver.disableUpdate = true
statusesState = .display(statuses: statuses, nextPageState: nextPageState)
scrollToIndex = newStatuses.count + 1

DispatchQueue.main.async { [weak self] in
self?.pendingStatusesObserver.disableUpdate = false
self?.canStreamEvents = true
}
}

// Refresh the timeline while keeping the user current position.
// It works because a side effect of withAnimation is that it keep scroll position IF the List is not scrolled to the top.
private func updateTimelineWithAnimation(statuses: [Status], nextPageState: StatusesState.PagingState) {
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: nextPageState)
canStreamEvents = true
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
}
}
}

enum NextPageError: Error {
case internalError
}
Expand Down

0 comments on commit 9ec6a4e

Please sign in to comment.