Show jump to present toast if necessary when scene re-appears

This commit is contained in:
Shadowfacts 2022-11-19 13:09:37 -05:00
parent ce534c4a05
commit 9ff1452c68
1 changed files with 80 additions and 41 deletions

View File

@ -125,6 +125,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
cell.update() cell.update()
} }
} }
NotificationCenter.default.addObserver(self, selector: #selector(sceneWillEnterForeground), name: UIScene.willEnterForegroundNotification, object: nil)
} }
// separate method because InstanceTimelineViewController needs to be able to customize it // separate method because InstanceTimelineViewController needs to be able to customize it
@ -242,6 +244,20 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
// } // }
// } // }
@objc private func sceneWillEnterForeground(_ notification: Foundation.Notification) {
guard let scene = notification.object as? UIScene,
// view.window is nil when the VC is not on screen
view.window?.windowScene == scene else {
return
}
Task {
if case .idle = controller.state,
let presentItems = try? await loadInitial() {
insertPresentItemsIfNecessary(presentItems)
}
}
}
@objc func refresh() { @objc func refresh() {
Task { Task {
if case .notLoadedInitial = controller.state { if case .notLoadedInitial = controller.state {
@ -249,24 +265,48 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
} else { } else {
// 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 (controller.loadNewer(), try? loadInitial()) let (_, presentItems) = await (controller.loadNewer(), try? loadInitial())
if let presentItems, if let presentItems {
case .status(id: let id, state: _) = dataSource.snapshot().itemIdentifiers(inSection: .statuses).first { insertPresentItemsIfNecessary(presentItems)
// if there's no overlap between presentItems and the existing items in the data source, prompt the user to scroll to present }
if !presentItems.contains(id) { }
var snapshot = self.dataSource.snapshot() #if !targetEnvironment(macCatalyst)
let currentItems = snapshot.itemIdentifiers(inSection: .statuses) collectionView.refreshControl?.endRefreshing()
guard let item = currentItems.first, #endif
case .status(id: id, state: _) = item else { }
return
} }
// remove any existing gap if there is one private func insertPresentItemsIfNecessary(_ presentItems: [String]) {
var snapshot = dataSource.snapshot()
let currentItems = snapshot.itemIdentifiers(inSection: .statuses)
if case .status(id: let firstID, state: _) = currentItems.first,
// if there's no overlap between presentItems and the existing items in the data source, insert the present items and prompt the user
!presentItems.contains(firstID) {
let applySnapshotBeforeScrolling: Bool
// remove any existing gap, if there is one
if let index = currentItems.lastIndex(of: .gap) { if let index = currentItems.lastIndex(of: .gap) {
snapshot.deleteItems(Array(currentItems[index...])) snapshot.deleteItems(Array(currentItems[index...]))
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
} }
snapshot.insertItems([.gap], beforeItem: item) } else {
// there is no existing gap
applySnapshotBeforeScrolling = true
}
snapshot.insertItems([.gap], beforeItem: currentItems.first!)
snapshot.insertItems(presentItems.map { .status(id: $0, state: .unknown) }, beforeItem: .gap) snapshot.insertItems(presentItems.map { .status(id: $0, state: .unknown) }, beforeItem: .gap)
if applySnapshotBeforeScrolling {
// use a snapshot of the collection view to hide the flicker as the content offset changes and then changes back // use a snapshot of the collection view to hide the flicker as the content offset changes and then changes back
let snapshotView = collectionView.snapshotView(afterScreenUpdates: false)! let snapshotView = collectionView.snapshotView(afterScreenUpdates: false)!
snapshotView.layer.zPosition = 1000 snapshotView.layer.zPosition = 1000
@ -278,6 +318,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset) self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
snapshotView.removeFromSuperview() snapshotView.removeFromSuperview()
} }
}
var config = ToastConfiguration(title: "Jump to present") var config = ToastConfiguration(title: "Jump to present")
config.edge = .top config.edge = .top
@ -286,17 +327,15 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
config.action = { [unowned self] toast in config.action = { [unowned self] toast in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
if !applySnapshotBeforeScrolling {
self.dataSource.apply(snapshot, animatingDifferences: false)
}
self.collectionView.scrollToTop() self.collectionView.scrollToTop()
} }
self.showToast(configuration: config, animated: true) self.showToast(configuration: config, animated: true)
} }
} }
}
#if !targetEnvironment(macCatalyst)
collectionView.refreshControl?.endRefreshing()
#endif
}
}
} }