forked from shadowfacts/Tusker
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
|
||||
let (_, presentItems) = await (loadNewerAndEndRefreshing(), try? loadInitial())
|
||||
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)))
|
||||
}
|
||||
} else {
|
||||
insertPresentItemsIfNecessary(presentItems)
|
||||
insertPresentItemsAndShowJumpToast(presentItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func insertPresentItemsIfNecessary(_ presentItems: [String]) {
|
||||
let snapshot = dataSource.snapshot()
|
||||
private func insertPresentItemsAndShowJumpToast(_ presentItems: [String]) {
|
||||
var snapshot = dataSource.snapshot()
|
||||
guard snapshot.indexOfSection(.statuses) != nil else {
|
||||
return
|
||||
}
|
||||
|
@ -444,13 +444,38 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
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
|
||||
!presentItems.contains(firstID) {
|
||||
let applySnapshotBeforeScrolling: Bool
|
||||
|
||||
// create a new snapshot to reset the timeline to the "present" state
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.statuses])
|
||||
snapshot.appendItems(presentItems.map { .status(id: $0, collapseState: .unknown, filterState: .unknown) }, toSection: .statuses)
|
||||
// remove any existing gap, if there is one
|
||||
if let index = currentItems.lastIndex(of: .gap) {
|
||||
snapshot.deleteItems(Array(currentItems[index...]))
|
||||
|
||||
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.systemImageName = "arrow.up"
|
||||
config.dismissAutomaticallyAfter = 4
|
||||
|
@ -468,18 +493,23 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
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)
|
||||
|
||||
var config = ToastConfiguration(title: "Go back")
|
||||
var config = ToastConfiguration(title: "Go Back")
|
||||
config.edge = .top
|
||||
config.systemImageName = "arrow.down"
|
||||
config.dismissAutomaticallyAfter = 4
|
||||
config.dismissAutomaticallyAfter = 2
|
||||
config.action = { [unowned self] toast in
|
||||
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
|
||||
if let (item, offset) = origItemAtTop {
|
||||
self.applySnapshot(snapshot, maintainingScreenPosition: offset, ofItem: item)
|
||||
self.applySnapshot(origSnapshot, maintainingScreenPosition: offset, ofItem: item)
|
||||
} else {
|
||||
self.dataSource.apply(origSnapshot, animatingDifferences: false)
|
||||
}
|
||||
|
@ -516,12 +546,17 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
// firstItemAfterOriginalGapOffsetFromTop without intruding into unmeasured area
|
||||
var cur = indexPathOfItemToMaintain
|
||||
var amountScrolledUp: CGFloat = 0
|
||||
var first = true
|
||||
while true {
|
||||
if cur.row <= 0 {
|
||||
break
|
||||
}
|
||||
if let cell = self.collectionView.cellForItem(at: indexPathOfItemToMaintain),
|
||||
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
|
||||
}
|
||||
cur = IndexPath(row: cur.row - 1, section: cur.section)
|
||||
|
@ -529,6 +564,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
self.collectionView.layoutIfNeeded()
|
||||
let attrs = self.collectionView.layoutAttributesForItem(at: cur)!
|
||||
amountScrolledUp += attrs.size.height
|
||||
first = false
|
||||
}
|
||||
self.collectionView.contentOffset.y += amountScrolledUp
|
||||
self.collectionView.contentOffset.y -= offsetFromTop
|
||||
|
|
Loading…
Reference in New Issue