Show jump to present toast if necessary when scene re-appears
This commit is contained in:
parent
ce534c4a05
commit
9ff1452c68
|
@ -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,47 +265,8 @@ 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()
|
|
||||||
let currentItems = snapshot.itemIdentifiers(inSection: .statuses)
|
|
||||||
guard let item = currentItems.first,
|
|
||||||
case .status(id: id, state: _) = item else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any existing gap if there is one
|
|
||||||
if let index = currentItems.lastIndex(of: .gap) {
|
|
||||||
snapshot.deleteItems(Array(currentItems[index...]))
|
|
||||||
}
|
|
||||||
snapshot.insertItems([.gap], beforeItem: item)
|
|
||||||
snapshot.insertItems(presentItems.map { .status(id: $0, state: .unknown) }, beforeItem: .gap)
|
|
||||||
|
|
||||||
// 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)!
|
|
||||||
snapshotView.layer.zPosition = 1000
|
|
||||||
snapshotView.frame = view.bounds
|
|
||||||
view.addSubview(snapshotView)
|
|
||||||
|
|
||||||
let bottomOffset = collectionView.contentSize.height - collectionView.contentOffset.y
|
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: false) {
|
|
||||||
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
|
|
||||||
snapshotView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = ToastConfiguration(title: "Jump to present")
|
|
||||||
config.edge = .top
|
|
||||||
config.systemImageName = "arrow.up"
|
|
||||||
config.dismissAutomaticallyAfter = 4
|
|
||||||
config.action = { [unowned self] toast in
|
|
||||||
toast.dismissToast(animated: true)
|
|
||||||
|
|
||||||
self.collectionView.scrollToTop()
|
|
||||||
}
|
|
||||||
self.showToast(configuration: config, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
|
@ -298,6 +275,68 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
|
||||||
|
if applySnapshotBeforeScrolling {
|
||||||
|
// 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)!
|
||||||
|
snapshotView.layer.zPosition = 1000
|
||||||
|
snapshotView.frame = view.bounds
|
||||||
|
view.addSubview(snapshotView)
|
||||||
|
|
||||||
|
let bottomOffset = collectionView.contentSize.height - collectionView.contentOffset.y
|
||||||
|
self.dataSource.apply(snapshot, animatingDifferences: false) {
|
||||||
|
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
|
||||||
|
snapshotView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = ToastConfiguration(title: "Jump to present")
|
||||||
|
config.edge = .top
|
||||||
|
config.systemImageName = "arrow.up"
|
||||||
|
config.dismissAutomaticallyAfter = 4
|
||||||
|
config.action = { [unowned self] toast in
|
||||||
|
toast.dismissToast(animated: true)
|
||||||
|
|
||||||
|
if !applySnapshotBeforeScrolling {
|
||||||
|
self.dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.collectionView.scrollToTop()
|
||||||
|
}
|
||||||
|
self.showToast(configuration: config, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineViewController {
|
extension TimelineViewController {
|
||||||
|
|
Loading…
Reference in New Issue