mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
233 lines
9.6 KiB
Swift
233 lines
9.6 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, statusBarHeight >= 39.0 {
|
|
if layout.deviceMetrics.hasDynamicIsland {
|
|
contentInsets.top = statusBarHeight
|
|
} else if statusBarHeight >= 44.0 {
|
|
contentInsets.top += 34.0
|
|
} else {
|
|
contentInsets.top += 29.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
|
|
}
|
|
}
|
|
}
|