import Foundation import UIKit import Display import AsyncDisplayKit import SwiftSignalKit import TelegramPresentationData private final class NotificationContainerControllerNodeView: UITracingLayerView { var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return self.hitTestImpl?(point, event) } } final class NotificationContainerControllerNode: ASDisplayNode { private var validLayout: ContainerViewLayout? private var topItemAndNode: (NotificationItem, NotificationItemContainerNode)? var displayingItemsUpdated: ((Bool) -> Void)? private var timeoutTimer: SwiftSignalKit.Timer? private var presentationData: PresentationData init(presentationData: PresentationData) { self.presentationData = presentationData super.init() self.setViewBlock({ return NotificationContainerControllerNodeView() }) self.backgroundColor = nil self.isOpaque = false } func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData } override func didLoad() { super.didLoad() (self.view as! NotificationContainerControllerNodeView).hitTestImpl = { [weak self] point, event in return self?.hitTest(point, with: event) } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let (_, topItemNode) = self.topItemAndNode { return topItemNode.hitTest(point, with: event) } return nil } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout if let (_, topItemNode) = self.topItemAndNode { transition.updateFrame(node: topItemNode, frame: CGRect(origin: CGPoint(), size: layout.size)) topItemNode.updateLayout(layout: layout, transition: transition) } } func removeItemsWithGroupingKey(_ key: AnyHashable) { if let (item, topItemNode) = self.topItemAndNode { if item.groupingKey == key { self.topItemAndNode = nil self.displayingItemsUpdated?(false) topItemNode.animateOut(completion: { [weak topItemNode] in topItemNode?.removeFromSupernode() }) } } } func enqueue(_ item: NotificationItem) { if let (_, topItemNode) = self.topItemAndNode { topItemNode.animateOut(completion: { [weak topItemNode] in topItemNode?.removeFromSupernode() }) } var useCompactLayout = false if let validLayout = self.validLayout { useCompactLayout = min(validLayout.size.width, validLayout.size.height) < 375.0 } let itemNode = item.node(compact: useCompactLayout) let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme) containerNode.item = item containerNode.contentNode = itemNode containerNode.dismissed = { [weak self] item in if let strongSelf = self { if let (topItem, topItemNode) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey { topItemNode.removeFromSupernode() strongSelf.topItemAndNode = nil if let strongSelf = self, strongSelf.topItemAndNode == nil { strongSelf.displayingItemsUpdated?(false) } } } } containerNode.cancelTimeout = { [weak self] item in if let strongSelf = self { if let (topItem, _) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey { strongSelf.timeoutTimer?.invalidate() strongSelf.timeoutTimer = nil } } } containerNode.resumeTimeout = { [weak self] item in if let strongSelf = self { if let (topItem, _) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey { strongSelf.resetTimeoutTimer() } } } self.topItemAndNode = (item, containerNode) self.addSubnode(containerNode) if let validLayout = self.validLayout { containerNode.updateLayout(layout: validLayout, transition: .immediate) containerNode.frame = CGRect(origin: CGPoint(), size: validLayout.size) containerNode.animateIn() } self.displayingItemsUpdated?(true) self.resetTimeoutTimer() } func removeItems(_ f: (NotificationItem) -> Bool) { if let (topItem, topItemNode) = self.topItemAndNode { if f(topItem) { self.topItemAndNode = nil topItemNode.animateOut(completion: { [weak self, weak topItemNode] in topItemNode?.removeFromSupernode() if let strongSelf = self, strongSelf.topItemAndNode == nil { strongSelf.displayingItemsUpdated?(false) } }) } } } private func resetTimeoutTimer() { self.timeoutTimer?.invalidate() let timeout: Double #if DEBUG timeout = 6.0 #else timeout = 5.0 #endif let timeoutTimer = SwiftSignalKit.Timer(timeout: 5.0, repeat: false, completion: { [weak self] in if let strongSelf = self { if let (_, topItemNode) = strongSelf.topItemAndNode { strongSelf.topItemAndNode = nil strongSelf.displayingItemsUpdated?(false) topItemNode.animateOut(completion: { [weak topItemNode] in topItemNode?.removeFromSupernode() }) } } }, queue: Queue.mainQueue()) self.timeoutTimer = timeoutTimer timeoutTimer.start() } }