import Foundation import UIKit import Display import AppBundle import HierarchyTrackingLayer private let shadowImage: UIImage? = { UIImage(named: "Stories/PanelGradient") }() public final class TextLoadingEffectView: UIView { let hierarchyTrackingLayer: HierarchyTrackingLayer private let maskContentsView: UIView private let maskHighlightNode: LinkHighlightingNode private let maskBorderContentsView: UIView private let maskBorderHighlightNode: LinkHighlightingNode private let backgroundView: UIImageView private let borderBackgroundView: UIImageView private let duration: Double private let gradientWidth: CGFloat private var size: CGSize? override public init(frame: CGRect) { self.hierarchyTrackingLayer = HierarchyTrackingLayer() self.maskContentsView = UIView() self.maskHighlightNode = LinkHighlightingNode(color: .black) //self.maskHighlightNode.useModernPathCalculation = true self.maskBorderContentsView = UIView() self.maskBorderHighlightNode = LinkHighlightingNode(color: .black) self.maskBorderHighlightNode.borderOnly = true //self.maskBorderHighlightNode.useModernPathCalculation = true self.maskBorderContentsView.addSubview(self.maskBorderHighlightNode.view) self.backgroundView = UIImageView() self.borderBackgroundView = UIImageView() self.gradientWidth = 120.0 self.duration = 1.0 super.init(frame: frame) self.isUserInteractionEnabled = false self.maskContentsView.mask = self.maskHighlightNode.view self.maskContentsView.addSubview(self.backgroundView) self.addSubview(self.maskContentsView) self.maskBorderContentsView.mask = self.maskBorderHighlightNode.view self.maskBorderContentsView.addSubview(self.borderBackgroundView) self.addSubview(self.maskBorderContentsView) let generateGradient: (CGFloat) -> UIImage? = { baseAlpha in return generateImage(CGSize(width: self.gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) let foregroundColor = UIColor(white: 1.0, alpha: min(1.0, baseAlpha * 4.0)) if let shadowImage { UIGraphicsPushContext(context) for i in 0 ..< 2 { let shadowFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (size.width * 0.5), y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)) context.saveGState() context.translateBy(x: shadowFrame.midX, y: shadowFrame.midY) context.rotate(by: CGFloat(i == 0 ? 1.0 : -1.0) * CGFloat.pi * 0.5) let adjustedRect = CGRect(origin: CGPoint(x: -shadowFrame.height * 0.5, y: -shadowFrame.width * 0.5), size: CGSize(width: shadowFrame.height, height: shadowFrame.width)) context.clip(to: adjustedRect, mask: shadowImage.cgImage!) context.setFillColor(foregroundColor.cgColor) context.fill(adjustedRect) context.restoreGState() } UIGraphicsPopContext() } })?.withRenderingMode(.alwaysTemplate) } self.backgroundView.image = generateGradient(0.5) self.borderBackgroundView.image = generateGradient(1.0) self.layer.addSublayer(self.hierarchyTrackingLayer) self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in guard let self, let size = self.size else { return } self.updateAnimations(size: size) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func updateAnimations(size: CGSize) { if self.backgroundView.layer.animation(forKey: "shimmer") != nil { return } let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.0) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true) animation.repeatCount = Float.infinity self.backgroundView.layer.add(animation, forKey: "shimmer") self.borderBackgroundView.layer.add(animation, forKey: "shimmer") } public func update(color: UIColor, textNode: TextNode, range: NSRange) { var rectsSet: [CGRect] = [] if let cachedLayout = textNode.cachedLayout { if let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty { rectsSet = rects } } let maskFrame = CGRect(origin: CGPoint(), size: textNode.bounds.size).insetBy(dx: -4.0, dy: -4.0) self.maskContentsView.backgroundColor = color.withAlphaComponent(0.1) self.maskBorderContentsView.backgroundColor = color.withAlphaComponent(0.12) self.backgroundView.tintColor = color self.borderBackgroundView.tintColor = color self.maskContentsView.frame = maskFrame self.maskBorderContentsView.frame = maskFrame self.maskHighlightNode.updateRects(rectsSet) self.maskHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize()) self.maskBorderHighlightNode.updateRects(rectsSet) self.maskBorderHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize()) if self.size != maskFrame.size { self.size = maskFrame.size self.backgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height)) self.borderBackgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height)) self.updateAnimations(size: maskFrame.size) } } }