Show toast when there are no new posts

This commit is contained in:
Shadowfacts 2021-08-15 18:19:25 -04:00
parent 1fda4248ec
commit f109253bba
4 changed files with 79 additions and 19 deletions

View File

@ -236,6 +236,18 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
snapshot.appendItems(newIdentifiers, toSection: .statuses) snapshot.appendItems(newIdentifiers, toSection: .statuses)
} }
completion(.success(snapshot)) completion(.success(snapshot))
if statuses.isEmpty {
DispatchQueue.main.async {
var config = ToastConfiguration(title: "You're all caught up")
config.edge = .top
config.dismissAutomaticallyAfter = 2
config.action = { (toast) in
toast.dismissToast(animated: true)
}
self.showToast(configuration: config, animated: true)
}
}
} }
} }
} }

View File

@ -10,6 +10,7 @@ import UIKit
struct ToastConfiguration { struct ToastConfiguration {
var systemImageName: String? var systemImageName: String?
var titleFont: UIFont = .boldSystemFont(ofSize: 14)
var title: String var title: String
var subtitle: String? var subtitle: String?
var actionTitle: String? var actionTitle: String?
@ -17,6 +18,7 @@ struct ToastConfiguration {
var edgeSpacing: CGFloat = 8 var edgeSpacing: CGFloat = 8
var edge: Edge = .automatic var edge: Edge = .automatic
var dismissOnScroll = true var dismissOnScroll = true
var dismissAutomaticallyAfter: TimeInterval? = nil
init(title: String) { init(title: String) {
self.title = title self.title = title

View File

@ -14,6 +14,8 @@ class ToastView: UIView {
private var shrinkAnimator: UIViewPropertyAnimator? private var shrinkAnimator: UIViewPropertyAnimator?
private var recognizedGesture = false private var recognizedGesture = false
private var shouldDismissOnScroll = false
private(set) var shouldDismissAutomatically = true
private var offscreenTranslation: CGFloat { private var offscreenTranslation: CGFloat {
var translation = bounds.height + configuration.edgeSpacing var translation = bounds.height + configuration.edgeSpacing
@ -62,7 +64,7 @@ class ToastView: UIView {
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.text = configuration.title titleLabel.text = configuration.title
titleLabel.textColor = .white titleLabel.textColor = .white
titleLabel.font = .boldSystemFont(ofSize: 14) titleLabel.font = configuration.titleFont
titleLabel.adjustsFontSizeToFitWidth = true titleLabel.adjustsFontSizeToFitWidth = true
if let subtitle = configuration.subtitle { if let subtitle = configuration.subtitle {
@ -109,10 +111,40 @@ class ToastView: UIView {
layer.shadowPath = CGPath(roundedRect: bounds, cornerWidth: layer.cornerRadius, cornerHeight: layer.cornerRadius, transform: nil) layer.shadowPath = CGPath(roundedRect: bounds, cornerWidth: layer.cornerRadius, cornerHeight: layer.cornerRadius, transform: nil)
} }
func dismissToast(animated: Bool) {
guard animated else {
removeFromSuperview()
return
}
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
self.transform = CGAffineTransform(translationX: 0, y: self.offscreenTranslation)
} completion: { (_) in
self.removeFromSuperview()
}
}
func animateAppearance() {
self.transform = CGAffineTransform(translationX: 0, y: offscreenTranslation)
let duration = 0.5
let velocity = 0.5
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: []) {
self.transform = .identity
}
}
func setupDismissOnScroll(connectedTo scrollView: UIScrollView) {
guard configuration.dismissOnScroll else { return }
scrollView.panGestureRecognizer.addTarget(self, action: #selector(scrollViewPanGestureRecognized))
}
// MARK: - Interaction
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event) super.touchesBegan(touches, with: event)
recognizedGesture = false recognizedGesture = false
shouldDismissAutomatically = false
shrinkAnimator = UIViewPropertyAnimator(duration: 0.1, curve: .easeInOut) { shrinkAnimator = UIViewPropertyAnimator(duration: 0.1, curve: .easeInOut) {
self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95) self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
} }
@ -145,6 +177,7 @@ class ToastView: UIView {
switch recognizer.state { switch recognizer.state {
case .began: case .began:
recognizedGesture = true recognizedGesture = true
shouldDismissAutomatically = false
UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) { UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) {
self.transform = .identity self.transform = .identity
} }
@ -199,24 +232,23 @@ class ToastView: UIView {
} }
} }
func dismissToast(animated: Bool) { @objc private func scrollViewPanGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
guard animated else { switch recognizer.state {
removeFromSuperview() case .began:
return shouldDismissOnScroll = true
}
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
self.transform = CGAffineTransform(translationX: 0, y: self.offscreenTranslation)
} completion: { (_) in
self.removeFromSuperview()
}
}
func animateAppearance() { case .changed:
self.transform = CGAffineTransform(translationX: 0, y: offscreenTranslation) let translation = recognizer.translation(in: recognizer.view).y
let duration = 0.5 if shouldDismissOnScroll && abs(translation) > 50 {
let velocity = 0.5 dismissToast(animated: true)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: []) { shouldDismissOnScroll = false
self.transform = .identity }
case .ended, .cancelled:
shouldDismissOnScroll = false
default:
break
} }
} }

View File

@ -11,6 +11,7 @@ import UIKit
protocol ToastableViewController: UIViewController { protocol ToastableViewController: UIViewController {
var toastParentView: UIView { get } var toastParentView: UIView { get }
var toastScrollView: UIScrollView? { get }
} }
@ -33,6 +34,7 @@ extension ToastableViewController {
} }
var toastParentView: UIView { view } var toastParentView: UIView { view }
var toastScrollView: UIScrollView? { view as? UIScrollView }
func showToast(configuration config: ToastConfiguration, animated: Bool) { func showToast(configuration config: ToastConfiguration, animated: Bool) {
currentToast?.dismissToast(animated: false) currentToast?.dismissToast(animated: false)
@ -67,6 +69,18 @@ extension ToastableViewController {
if animated { if animated {
toast.animateAppearance() toast.animateAppearance()
} }
if config.dismissOnScroll,
let scrollView = toastScrollView {
toast.setupDismissOnScroll(connectedTo: scrollView)
}
if let time = config.dismissAutomaticallyAfter {
DispatchQueue.main.asyncAfter(deadline: .now() + time) { [weak toast] in
guard let toast = toast, toast.shouldDismissAutomatically else { return }
toast.dismissToast(animated: true)
}
}
} }
private func effectiveEdge(edge: ToastConfiguration.Edge) -> ToastConfiguration.Edge { private func effectiveEdge(edge: ToastConfiguration.Edge) -> ToastConfiguration.Edge {