import Foundation import UIKit import Display import AsyncDisplayKit import SwiftSignalKit private let compactNameFont = Font.regular(28.0) private let regularNameFont = Font.regular(36.0) private let compactStatusFont = Font.regular(18.0) private let regularStatusFont = Font.regular(18.0) enum LegacyCallControllerStatusValue: Equatable { case text(String) case timer((String) -> String, Double) static func ==(lhs: LegacyCallControllerStatusValue, rhs: LegacyCallControllerStatusValue) -> Bool { switch lhs { case let .text(text): if case .text(text) = rhs { return true } else { return false } case let .timer(_, referenceTime): if case .timer(_, referenceTime) = rhs { return true } else { return false } } } } final class LegacyCallControllerStatusNode: ASDisplayNode { private let titleNode: TextNode private let statusNode: TextNode private let statusMeasureNode: TextNode private let receptionNode: LegacyCallControllerReceptionNode var title: String = "" var subtitle: String = "" var status: LegacyCallControllerStatusValue = .text("") { didSet { if self.status != oldValue { self.statusTimer?.invalidate() if case .timer = self.status { self.statusTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in if let strongSelf = self, let validLayoutWidth = strongSelf.validLayoutWidth { let _ = strongSelf.updateLayout(constrainedWidth: validLayoutWidth, transition: .immediate) } }, queue: Queue.mainQueue()) self.statusTimer?.start() } else { if let validLayoutWidth = self.validLayoutWidth { let _ = self.updateLayout(constrainedWidth: validLayoutWidth, transition: .immediate) } } } } } var reception: Int32? { didSet { if self.reception != oldValue { if let reception = self.reception { self.receptionNode.reception = reception if oldValue == nil { let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring) transition.updateAlpha(node: self.receptionNode, alpha: 1.0) } } else if self.reception == nil, oldValue != nil { let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring) transition.updateAlpha(node: self.receptionNode, alpha: 0.0) } if (oldValue == nil) != (self.reception != nil) { if let validLayoutWidth = self.validLayoutWidth { let _ = self.updateLayout(constrainedWidth: validLayoutWidth, transition: .immediate) } } } } } private var statusTimer: SwiftSignalKit.Timer? private var validLayoutWidth: CGFloat? override init() { self.titleNode = TextNode() self.statusNode = TextNode() self.statusNode.displaysAsynchronously = false self.statusMeasureNode = TextNode() self.receptionNode = LegacyCallControllerReceptionNode() self.receptionNode.alpha = 0.0 super.init() self.isUserInteractionEnabled = false self.addSubnode(self.titleNode) self.addSubnode(self.statusNode) self.addSubnode(self.receptionNode) } deinit { self.statusTimer?.invalidate() } func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayoutWidth = constrainedWidth let nameFont: UIFont let statusFont: UIFont if constrainedWidth < 330.0 { nameFont = compactNameFont statusFont = compactStatusFont } else { nameFont = regularNameFont statusFont = regularStatusFont } var statusOffset: CGFloat = 0.0 let statusText: String let statusMeasureText: String switch self.status { case let .text(text): statusText = text statusMeasureText = text case let .timer(format, referenceTime): let duration = Int32(CFAbsoluteTimeGetCurrent() - referenceTime) let durationString: String let measureDurationString: String if duration > 60 * 60 { durationString = String(format: "%02d:%02d:%02d", arguments: [duration / 3600, (duration / 60) % 60, duration % 60]) measureDurationString = "00:00:00" } else { durationString = String(format: "%02d:%02d", arguments: [(duration / 60) % 60, duration % 60]) measureDurationString = "00:00" } statusText = format(durationString) statusMeasureText = format(measureDurationString) if self.reception != nil { statusOffset += 8.0 } } let spacing: CGFloat = 4.0 let (titleLayout, titleApply) = TextNode.asyncLayout(self.titleNode)(TextNodeLayoutArguments(attributedString: NSAttributedString(string: self.title, font: nameFont, textColor: .white), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0))) let (statusMeasureLayout, statusMeasureApply) = TextNode.asyncLayout(self.statusMeasureNode)(TextNodeLayoutArguments(attributedString: NSAttributedString(string: statusMeasureText, font: statusFont, textColor: .white), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0))) let (statusLayout, statusApply) = TextNode.asyncLayout(self.statusNode)(TextNodeLayoutArguments(attributedString: NSAttributedString(string: statusText, font: statusFont, textColor: .white), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0))) let _ = titleApply() let _ = statusApply() let _ = statusMeasureApply() self.titleNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - titleLayout.size.width) / 2.0), y: 0.0), size: titleLayout.size) self.statusNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - statusMeasureLayout.size.width) / 2.0) + statusOffset, y: titleLayout.size.height + spacing), size: statusLayout.size) self.receptionNode.frame = CGRect(origin: CGPoint(x: self.statusNode.frame.minX - receptionNodeSize.width, y: titleLayout.size.height + spacing + 9.0), size: receptionNodeSize) return titleLayout.size.height + spacing + statusLayout.size.height } } private final class CallControllerReceptionNodeParameters: NSObject { let reception: Int32 init(reception: Int32) { self.reception = reception } } private let receptionNodeSize = CGSize(width: 24.0, height: 10.0) final class LegacyCallControllerReceptionNode : ASDisplayNode { var reception: Int32 = 4 { didSet { self.setNeedsDisplay() } } override init() { super.init() self.isOpaque = false self.isLayerBacked = true } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { return CallControllerReceptionNodeParameters(reception: self.reception) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { let context = UIGraphicsGetCurrentContext()! context.setFillColor(UIColor.white.cgColor) if let parameters = parameters as? CallControllerReceptionNodeParameters{ let width: CGFloat = 3.0 var spacing: CGFloat = 1.5 if UIScreenScale > 2 { spacing = 4.0 / 3.0 } for i in 0 ..< 4 { let height = 4.0 + 2.0 * CGFloat(i) let rect = CGRect(x: bounds.minX + CGFloat(i) * (width + spacing), y: receptionNodeSize.height - height, width: width, height: height) if i >= parameters.reception { context.setAlpha(0.4) } let path = UIBezierPath(roundedRect: rect, cornerRadius: 0.5) context.addPath(path.cgPath) context.fillPath() } } } }