import Foundation import UIKit import AsyncDisplayKit import Display private let backgroundImage = generateStretchableFilledCircleImage(radius: 4.0, color: UIColor(white: 0.0, alpha: 0.5)) final class ListMessagePlaybackOverlayNode: ASDisplayNode { private let backgroundNode: ASImageNode private let barNodes: [ASDisplayNode] var isPlaying: Bool = false { didSet { if self.isPlaying != oldValue { if self.isInHierarchy { if self.isPlaying { self.animateToPlaying() } else { self.animateToPaused() } } } } } override init() { self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.image = backgroundImage let baseSize = CGSize(width: 48.0, height: 48.0) let barSize = CGSize(width: 3.0, height: 13.0) let barSpacing: CGFloat = 2.0 self.backgroundNode.frame = CGRect(origin: CGPoint(), size: baseSize) let barsOrigin = CGPoint(x: floor((baseSize.width - (barSize.width * 4.0 + barSpacing * 3.0)) / 2.0), y: 23.0) var barNodes: [ASDisplayNode] = [] for i in 0 ..< 4 { let barNode = ASDisplayNode() barNode.frame = CGRect(origin: barsOrigin.offsetBy(dx: CGFloat(i) * (barSize.width + barSpacing), dy: 0.0), size: barSize) barNode.isLayerBacked = true barNode.backgroundColor = .white barNode.anchorPoint = CGPoint(x: 0.5, y: 1.0) barNode.transform = CATransform3DMakeScale(1.0, 0.2, 1.0) barNodes.append(barNode) } self.barNodes = barNodes super.init() self.isLayerBacked = true self.addSubnode(self.backgroundNode) for barNode in self.barNodes { self.addSubnode(barNode) } } override func willEnterHierarchy() { super.willEnterHierarchy() if self.isPlaying { self.animateToPlaying() } } override func didExitHierarchy() { super.didExitHierarchy() for barNode in self.barNodes { barNode.layer.removeAnimation(forKey: "transform.scale.y") } } private func animateToPlaying() { for barNode in self.barNodes { let randValueMul = Float(arc4random()) / Float(UInt32.max) let randDurationMul = Double(arc4random()) / Double(UInt32.max) let animation = CABasicAnimation(keyPath: "transform.scale.y") animation.toValue = Float(0.5 + 0.5 * randValueMul) as NSNumber animation.autoreverses = true animation.duration = 0.25 + 0.25 * randDurationMul animation.repeatCount = Float.greatestFiniteMagnitude; animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) barNode.layer.removeAnimation(forKey: "transform.scale.y") barNode.layer.add(animation, forKey: "transform.scale.y") } } private func animateToPaused() { for barNode in self.barNodes { if let presentationLayer = barNode.layer.presentation() { let animation = CABasicAnimation(keyPath: "transform.scale.y") animation.fromValue = (presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0 animation.toValue = 0.2 as NSNumber animation.duration = 0.25 barNode.layer.add(animation, forKey: "transform.scale.y") } } } }