mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
204 lines
8.8 KiB
Swift
204 lines
8.8 KiB
Swift
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
|
|
private final class ShimmerEffectForegroundNode: ASDisplayNode {
|
|
private var currentBackgroundColor: UIColor?
|
|
private var currentForegroundColor: UIColor?
|
|
private let imageNodeContainer: ASDisplayNode
|
|
private let imageNode: ASImageNode
|
|
|
|
private var absoluteLocation: (CGRect, CGSize)?
|
|
private var isCurrentlyInHierarchy = false
|
|
private var shouldBeAnimating = false
|
|
|
|
override init() {
|
|
self.imageNodeContainer = ASDisplayNode()
|
|
self.imageNodeContainer.isLayerBacked = true
|
|
|
|
self.imageNode = ASImageNode()
|
|
self.imageNode.isLayerBacked = true
|
|
self.imageNode.displaysAsynchronously = false
|
|
self.imageNode.displayWithoutProcessing = true
|
|
self.imageNode.contentMode = .scaleToFill
|
|
|
|
super.init()
|
|
|
|
self.isLayerBacked = true
|
|
self.clipsToBounds = true
|
|
|
|
self.imageNodeContainer.addSubnode(self.imageNode)
|
|
self.addSubnode(self.imageNodeContainer)
|
|
}
|
|
|
|
override func didEnterHierarchy() {
|
|
super.didEnterHierarchy()
|
|
|
|
self.isCurrentlyInHierarchy = true
|
|
self.updateAnimation()
|
|
}
|
|
|
|
override func didExitHierarchy() {
|
|
super.didExitHierarchy()
|
|
|
|
self.isCurrentlyInHierarchy = false
|
|
self.updateAnimation()
|
|
}
|
|
|
|
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
|
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
|
|
return
|
|
}
|
|
self.currentBackgroundColor = backgroundColor
|
|
self.currentForegroundColor = foregroundColor
|
|
|
|
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
|
|
context.setFillColor(backgroundColor.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
|
|
|
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
|
let peakColor = foregroundColor.cgColor
|
|
|
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
|
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
|
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
|
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
|
})
|
|
}
|
|
|
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
|
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
|
return
|
|
}
|
|
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
|
let frameUpdated = self.absoluteLocation?.0 != rect
|
|
self.absoluteLocation = (rect, containerSize)
|
|
|
|
if sizeUpdated {
|
|
if self.shouldBeAnimating {
|
|
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
|
self.addImageAnimation()
|
|
} else {
|
|
self.updateAnimation()
|
|
}
|
|
}
|
|
|
|
if frameUpdated {
|
|
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
|
}
|
|
}
|
|
|
|
private func updateAnimation() {
|
|
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
|
if shouldBeAnimating != self.shouldBeAnimating {
|
|
self.shouldBeAnimating = shouldBeAnimating
|
|
if shouldBeAnimating {
|
|
self.addImageAnimation()
|
|
} else {
|
|
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func addImageAnimation() {
|
|
guard let containerSize = self.absoluteLocation?.1 else {
|
|
return
|
|
}
|
|
let gradientHeight: CGFloat = 250.0
|
|
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
|
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = Float.infinity
|
|
animation.beginTime = 1.0
|
|
self.imageNode.layer.add(animation, forKey: "shimmer")
|
|
}
|
|
}
|
|
|
|
public final class ShimmerEffectNode: ASDisplayNode {
|
|
public enum Shape: Equatable {
|
|
case circle(CGRect)
|
|
case roundedRectLine(startPoint: CGPoint, width: CGFloat, diameter: CGFloat)
|
|
case roundedRect(rect: CGRect, cornerRadius: CGFloat)
|
|
case rect(rect: CGRect)
|
|
}
|
|
|
|
private let backgroundNode: ASDisplayNode
|
|
private let effectNode: ShimmerEffectForegroundNode
|
|
private let foregroundNode: ASImageNode
|
|
|
|
private var currentShapes: [Shape] = []
|
|
private var currentBackgroundColor: UIColor?
|
|
private var currentForegroundColor: UIColor?
|
|
private var currentShimmeringColor: UIColor?
|
|
private var currentSize = CGSize()
|
|
|
|
override public init() {
|
|
self.backgroundNode = ASDisplayNode()
|
|
|
|
self.effectNode = ShimmerEffectForegroundNode()
|
|
|
|
self.foregroundNode = ASImageNode()
|
|
self.foregroundNode.displaysAsynchronously = false
|
|
self.foregroundNode.displayWithoutProcessing = true
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.backgroundNode)
|
|
self.addSubnode(self.effectNode)
|
|
self.addSubnode(self.foregroundNode)
|
|
}
|
|
|
|
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
|
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
|
|
}
|
|
|
|
public func update(backgroundColor: UIColor, foregroundColor: UIColor, shimmeringColor: UIColor, shapes: [Shape], size: CGSize) {
|
|
if self.currentShapes == shapes, let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor), self.currentSize == size {
|
|
return
|
|
}
|
|
|
|
self.currentBackgroundColor = backgroundColor
|
|
self.currentForegroundColor = foregroundColor
|
|
self.currentShimmeringColor = shimmeringColor
|
|
self.currentShapes = shapes
|
|
self.currentSize = size
|
|
|
|
self.backgroundNode.backgroundColor = foregroundColor
|
|
|
|
self.effectNode.update(backgroundColor: foregroundColor, foregroundColor: shimmeringColor)
|
|
|
|
self.foregroundNode.image = generateImage(size, rotatedContext: { size, context in
|
|
context.setFillColor(backgroundColor.cgColor)
|
|
context.setBlendMode(.copy)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
for shape in shapes {
|
|
switch shape {
|
|
case let .circle(frame):
|
|
context.fillEllipse(in: frame)
|
|
case let .roundedRectLine(startPoint, width, diameter):
|
|
context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter)))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter)))
|
|
context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter)))
|
|
case let .roundedRect(rect, radius):
|
|
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft, .topRight, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: radius, height: radius))
|
|
UIGraphicsPushContext(context)
|
|
path.fill()
|
|
UIGraphicsPopContext()
|
|
case let .rect(rect):
|
|
context.fill(rect)
|
|
}
|
|
}
|
|
})
|
|
|
|
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
self.foregroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
}
|
|
}
|