More fiddling with how Jump to Present works
Now, when loading present items, they're inserted into the data source immediately along with a gap. If the user taps Jump to Present, then a new snapshot _with only the present items_ will be applied (which allows infinite scrolling to work properly when they scroll back down) and the view scrolled-to-top. Tapping Go Back, then, applies the original snapshot (i.e., the current one from when Jump to Present was tapped) and restores the scroll position.
This commit is contained in:
parent
97dec0f9d2
commit
0ce57d1308
|
@ -412,7 +412,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
// I'm not sure whether this should move into TimelineLikeController/TimelineLikeCollectionViewController
|
// I'm not sure whether this should move into TimelineLikeController/TimelineLikeCollectionViewController
|
||||||
let (_, presentItems) = await (loadNewerAndEndRefreshing(), try? loadInitial())
|
let (_, presentItems) = await (loadNewerAndEndRefreshing(), try? loadInitial())
|
||||||
if let presentItems, !presentItems.isEmpty {
|
if let presentItems, !presentItems.isEmpty {
|
||||||
insertPresentItemsIfNecessary(presentItems)
|
insertPresentItemsAndShowJumpToast(presentItems)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -430,13 +430,13 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
UIAccessibility.post(notification: .screenChanged, argument: self.collectionView.cellForItem(at: IndexPath(row: 0, section: 0)))
|
UIAccessibility.post(notification: .screenChanged, argument: self.collectionView.cellForItem(at: IndexPath(row: 0, section: 0)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
insertPresentItemsIfNecessary(presentItems)
|
insertPresentItemsAndShowJumpToast(presentItems)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func insertPresentItemsIfNecessary(_ presentItems: [String]) {
|
private func insertPresentItemsAndShowJumpToast(_ presentItems: [String]) {
|
||||||
let snapshot = dataSource.snapshot()
|
var snapshot = dataSource.snapshot()
|
||||||
guard snapshot.indexOfSection(.statuses) != nil else {
|
guard snapshot.indexOfSection(.statuses) != nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -444,13 +444,38 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
if case .status(id: let firstID, _, _) = currentItems.first,
|
if case .status(id: let firstID, _, _) = currentItems.first,
|
||||||
// if there's no overlap between presentItems and the existing items in the data source, prompt the user
|
// if there's no overlap between presentItems and the existing items in the data source, prompt the user
|
||||||
!presentItems.contains(firstID) {
|
!presentItems.contains(firstID) {
|
||||||
|
let applySnapshotBeforeScrolling: Bool
|
||||||
|
|
||||||
// create a new snapshot to reset the timeline to the "present" state
|
// remove any existing gap, if there is one
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
if let index = currentItems.lastIndex(of: .gap) {
|
||||||
snapshot.appendSections([.statuses])
|
snapshot.deleteItems(Array(currentItems[index...]))
|
||||||
snapshot.appendItems(presentItems.map { .status(id: $0, collapseState: .unknown, filterState: .unknown) }, toSection: .statuses)
|
|
||||||
|
|
||||||
var config = ToastConfiguration(title: "Jump to present")
|
let statusesSection = snapshot.indexOfSection(.statuses)!
|
||||||
|
if collectionView.indexPathsForVisibleItems.contains(IndexPath(row: index, section: statusesSection)) {
|
||||||
|
// the gap cell is on screen
|
||||||
|
applySnapshotBeforeScrolling = false
|
||||||
|
} else if let topMostVisibleCell = collectionView.indexPathsForVisibleItems.first(where: { $0.section == statusesSection }),
|
||||||
|
index < topMostVisibleCell.row {
|
||||||
|
// the gap cell is above the top, so applying the snapshot would remove the currently-viewed statuses
|
||||||
|
applySnapshotBeforeScrolling = false
|
||||||
|
} else {
|
||||||
|
// the gap cell is below the bottom of the screen
|
||||||
|
applySnapshotBeforeScrolling = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// there is no existing gap
|
||||||
|
applySnapshotBeforeScrolling = true
|
||||||
|
}
|
||||||
|
snapshot.insertItems([.gap], beforeItem: currentItems.first!)
|
||||||
|
snapshot.insertItems(presentItems.map { .status(id: $0, collapseState: .unknown, filterState: .unknown) }, beforeItem: .gap)
|
||||||
|
|
||||||
|
if applySnapshotBeforeScrolling {
|
||||||
|
let firstVisibleIndexPath = collectionView.indexPathsForVisibleItems.min()!
|
||||||
|
let firstVisibleItem = dataSource.itemIdentifier(for: firstVisibleIndexPath)!
|
||||||
|
applySnapshot(snapshot, maintainingBottomRelativeScrollPositionOf: firstVisibleItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = ToastConfiguration(title: "Jump to Present")
|
||||||
config.edge = .top
|
config.edge = .top
|
||||||
config.systemImageName = "arrow.up"
|
config.systemImageName = "arrow.up"
|
||||||
config.dismissAutomaticallyAfter = 4
|
config.dismissAutomaticallyAfter = 4
|
||||||
|
@ -468,18 +493,23 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
origItemAtTop = nil
|
origItemAtTop = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: true) {
|
// when the user explicitly taps Jump to Present, we drop all the old items to let infinite scrolling work properly when they scroll back down
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
|
snapshot.appendSections([.statuses])
|
||||||
|
snapshot.appendItems(presentItems.map { .status(id: $0, collapseState: .unknown, filterState: .unknown) }, toSection: .statuses)
|
||||||
|
// don't animate the snapshot change, the scrolling animation will paper over the switch
|
||||||
|
self.dataSource.apply(snapshot, animatingDifferences: false) {
|
||||||
self.collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
|
self.collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
|
||||||
|
|
||||||
var config = ToastConfiguration(title: "Go back")
|
var config = ToastConfiguration(title: "Go Back")
|
||||||
config.edge = .top
|
config.edge = .top
|
||||||
config.systemImageName = "arrow.down"
|
config.systemImageName = "arrow.down"
|
||||||
config.dismissAutomaticallyAfter = 4
|
config.dismissAutomaticallyAfter = 2
|
||||||
config.action = { [unowned self] toast in
|
config.action = { [unowned self] toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
// todo: it would be nice if we could animate this, but that doesn't work with the screen-position-maintaining stuff
|
// todo: it would be nice if we could animate this, but that doesn't work with the screen-position-maintaining stuff
|
||||||
if let (item, offset) = origItemAtTop {
|
if let (item, offset) = origItemAtTop {
|
||||||
self.applySnapshot(snapshot, maintainingScreenPosition: offset, ofItem: item)
|
self.applySnapshot(origSnapshot, maintainingScreenPosition: offset, ofItem: item)
|
||||||
} else {
|
} else {
|
||||||
self.dataSource.apply(origSnapshot, animatingDifferences: false)
|
self.dataSource.apply(origSnapshot, animatingDifferences: false)
|
||||||
}
|
}
|
||||||
|
@ -516,12 +546,17 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
// firstItemAfterOriginalGapOffsetFromTop without intruding into unmeasured area
|
// firstItemAfterOriginalGapOffsetFromTop without intruding into unmeasured area
|
||||||
var cur = indexPathOfItemToMaintain
|
var cur = indexPathOfItemToMaintain
|
||||||
var amountScrolledUp: CGFloat = 0
|
var amountScrolledUp: CGFloat = 0
|
||||||
|
var first = true
|
||||||
while true {
|
while true {
|
||||||
if cur.row <= 0 {
|
if cur.row <= 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if let cell = self.collectionView.cellForItem(at: indexPathOfItemToMaintain),
|
if let cell = self.collectionView.cellForItem(at: indexPathOfItemToMaintain),
|
||||||
cell.convert(.zero, to: self.view).y - self.view.safeAreaInsets.top > offsetFromTop {
|
cell.convert(.zero, to: self.view).y - self.view.safeAreaInsets.top > offsetFromTop {
|
||||||
|
// if we're breaking from the loop at the first iteration, we need to make sure to still call scrollToItem for the current row
|
||||||
|
if first {
|
||||||
|
self.collectionView.scrollToItem(at: cur, at: .top, animated: false)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
cur = IndexPath(row: cur.row - 1, section: cur.section)
|
cur = IndexPath(row: cur.row - 1, section: cur.section)
|
||||||
|
@ -529,6 +564,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
self.collectionView.layoutIfNeeded()
|
self.collectionView.layoutIfNeeded()
|
||||||
let attrs = self.collectionView.layoutAttributesForItem(at: cur)!
|
let attrs = self.collectionView.layoutAttributesForItem(at: cur)!
|
||||||
amountScrolledUp += attrs.size.height
|
amountScrolledUp += attrs.size.height
|
||||||
|
first = false
|
||||||
}
|
}
|
||||||
self.collectionView.contentOffset.y += amountScrolledUp
|
self.collectionView.contentOffset.y += amountScrolledUp
|
||||||
self.collectionView.contentOffset.y -= offsetFromTop
|
self.collectionView.contentOffset.y -= offsetFromTop
|
||||||
|
|
Loading…
Reference in New Issue