Swiftgram/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift
2020-02-20 18:08:36 +04:00

226 lines
9.3 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
final class NotificationItemContainerNode: ASDisplayNode {
private let backgroundNode: ASImageNode
private var validLayout: ContainerViewLayout?
var item: NotificationItem?
private var hapticFeedback: HapticFeedback?
private var willBeExpanded = false {
didSet {
if self.willBeExpanded != oldValue {
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
}
self.hapticFeedback?.impact()
}
}
}
var contentNode: NotificationItemNode? {
didSet {
if self.contentNode !== oldValue {
oldValue?.removeFromSupernode()
}
if let contentNode = self.contentNode {
self.addSubnode(contentNode)
if let validLayout = self.validLayout {
self.updateLayout(layout: validLayout, transition: .immediate)
}
}
}
}
var dismissed: ((NotificationItem) -> Void)?
var cancelTimeout: ((NotificationItem) -> Void)?
var resumeTimeout: ((NotificationItem) -> Void)?
var cancelledTimeout = false
init(theme: PresentationTheme) {
self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.image = PresentationResourcesRootController.inAppNotificationBackground(theme)
super.init()
self.addSubnode(self.backgroundNode)
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
panRecognizer.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(panRecognizer)
}
func animateIn() {
if let _ = self.validLayout {
self.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.backgroundNode.bounds.size.height), to: CGPoint(), duration: 0.4, additive: true)
}
}
func animateOut(completion: @escaping () -> Void) {
if let _ = self.validLayout {
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.backgroundNode.bounds.size.height), duration: 0.4, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
} else {
completion()
}
}
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
if let contentNode = self.contentNode {
let inset: CGFloat = 8.0
var contentInsets = UIEdgeInsets(top: inset, left: inset + layout.safeInsets.left, bottom: inset, right: inset + layout.safeInsets.right)
if let statusBarHeight = layout.statusBarHeight, CGFloat(44.0).isLessThanOrEqualTo(statusBarHeight) {
contentInsets.top += 34.0
}
let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
let contentWidth = containerWidth - contentInsets.left - contentInsets.right
let contentHeight = contentNode.updateLayout(width: contentWidth, transition: transition)
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - containerWidth - 8.0 * 2.0) / 2.0), y: contentInsets.top - 16.0), size: CGSize(width: containerWidth + 8.0 * 2.0, height: 8.0 + contentHeight + 20.0 + 8.0)))
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - contentWidth) / 2.0), y: contentInsets.top), size: CGSize(width: contentWidth, height: contentHeight)))
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let contentNode = self.contentNode, contentNode.frame.contains(point) {
return self.view
}
return nil
}
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if let item = self.item {
item.tapped({ [weak self] in
if let strongSelf = self, let contentNode = strongSelf.contentNode, let _ = strongSelf.item {
return (contentNode, {
if let strongSelf = self, let item = strongSelf.item {
strongSelf.dismissed?(item)
}
})
} else {
return (nil, {})
}
})
}
}
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
self.cancelledTimeout = false
case .changed:
let translation = recognizer.translation(in: self.view)
var bounds = self.bounds
bounds.origin.y = -translation.y
if bounds.origin.y < 0.0 {
let delta = -bounds.origin.y
bounds.origin.y = -((1.0 - (1.0 / (((delta) * 0.55 / (50.0)) + 1.0))) * 50.0)
}
if abs(translation.y) > 1.0 {
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
}
self.hapticFeedback?.prepareImpact()
}
self.bounds = bounds
var expand = false
if let item = self.item {
if !self.cancelledTimeout && abs(translation.y) > 4.0 {
self.cancelledTimeout = true
self.cancelTimeout?(item)
}
expand = item.canBeExpanded() && bounds.minY < -24.0
}
if self.willBeExpanded != expand {
self.willBeExpanded = expand
}
case .ended:
let translation = recognizer.translation(in: self.view)
var bounds = self.bounds
bounds.origin.y = -translation.y
if bounds.origin.y < 0.0 {
let delta = -bounds.origin.y
bounds.origin.y = -((1.0 - (1.0 / (((delta) * 0.55 / (50.0)) + 1.0))) * 50.0)
}
let velocity = recognizer.velocity(in: self.view)
if (bounds.minY < -20.0 || velocity.y > 300.0) {
if let item = self.item {
if !self.cancelledTimeout {
self.cancelledTimeout = true
self.cancelTimeout?(item)
}
item.expand({ [weak self] in
if let strongSelf = self, let contentNode = strongSelf.contentNode, let _ = strongSelf.item {
return (contentNode, {
if let strongSelf = self, let item = strongSelf.item {
strongSelf.dismissed?(item)
}
})
} else {
return (nil, {})
}
})
}
} else if bounds.minY > 5.0 || velocity.y < -200.0 {
self.animateOut(completion: { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
strongSelf.dismissed?(item)
}
})
} else {
if let item = self.item, self.cancelledTimeout {
self.cancelledTimeout = false
self.resumeTimeout?(item)
}
self.cancelledTimeout = false
let previousBounds = self.bounds
var bounds = self.bounds
bounds.origin.y = 0.0
self.bounds = bounds
self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
self.willBeExpanded = false
}
case .cancelled:
self.willBeExpanded = false
self.cancelledTimeout = false
let previousBounds = self.bounds
var bounds = self.bounds
bounds.origin.y = 0.0
self.bounds = bounds
self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
default:
break
}
}
}