mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-12 17:30:34 +00:00
202 lines
8.4 KiB
Swift
202 lines
8.4 KiB
Swift
import UIKit
|
|
import Display
|
|
import ComponentFlow
|
|
import TelegramPresentationData
|
|
|
|
private final class ShimmerEffectView: UIView {
|
|
private var currentBackgroundColor: UIColor?
|
|
private var currentForegroundColor: UIColor?
|
|
private let imageContainerView: UIView
|
|
private let imageView: UIImageView
|
|
|
|
private var absoluteLocation: (CGRect, CGSize)?
|
|
private var shouldBeAnimating = false
|
|
|
|
override init(frame: CGRect = .zero) {
|
|
self.imageContainerView = UIView()
|
|
self.imageContainerView.isUserInteractionEnabled = false
|
|
|
|
self.imageView = UIImageView()
|
|
self.imageView.isUserInteractionEnabled = false
|
|
self.imageView.contentMode = .scaleToFill
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.isUserInteractionEnabled = false
|
|
self.clipsToBounds = true
|
|
|
|
self.imageContainerView.addSubview(self.imageView)
|
|
self.addSubview(self.imageContainerView)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
preconditionFailure()
|
|
}
|
|
|
|
override func didMoveToWindow() {
|
|
super.didMoveToWindow()
|
|
updateAnimation()
|
|
}
|
|
|
|
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
|
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.argb == backgroundColor.argb,
|
|
let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.argb == foregroundColor.argb {
|
|
return
|
|
}
|
|
self.currentBackgroundColor = backgroundColor
|
|
self.currentForegroundColor = foregroundColor
|
|
|
|
self.imageView.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: .zero, size: size))
|
|
|
|
context.clip(to: CGRect(origin: .zero, 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: [])
|
|
})
|
|
}
|
|
|
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
|
if let 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, shouldBeAnimating {
|
|
self.imageView.layer.removeAnimation(forKey: "shimmer")
|
|
self.addImageAnimation()
|
|
}
|
|
|
|
if frameUpdated {
|
|
self.imageContainerView.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
|
}
|
|
|
|
self.updateAnimation()
|
|
}
|
|
|
|
private func updateAnimation() {
|
|
let inHierarchy = (self.window != nil)
|
|
let shouldAnimate = inHierarchy && (self.absoluteLocation != nil)
|
|
if shouldAnimate != self.shouldBeAnimating {
|
|
self.shouldBeAnimating = shouldAnimate
|
|
if shouldAnimate {
|
|
self.addImageAnimation()
|
|
} else {
|
|
self.imageView.layer.removeAnimation(forKey: "shimmer")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func addImageAnimation() {
|
|
guard let containerSize = self.absoluteLocation?.1 else { return }
|
|
let gradientHeight: CGFloat = 250.0
|
|
self.imageView.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight),
|
|
size: CGSize(width: containerSize.width, height: gradientHeight))
|
|
|
|
let anim = CABasicAnimation(keyPath: "position.y")
|
|
anim.fromValue = 0.0
|
|
anim.toValue = (containerSize.height + gradientHeight)
|
|
anim.duration = 1.3
|
|
anim.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
|
anim.fillMode = .removed
|
|
anim.isRemovedOnCompletion = true
|
|
anim.repeatCount = .infinity
|
|
anim.beginTime = CACurrentMediaTime() + 1.0
|
|
anim.isAdditive = true
|
|
|
|
self.imageView.layer.add(anim, forKey: "shimmer")
|
|
}
|
|
}
|
|
|
|
public final class GiftLoadingShimmerView: UIView {
|
|
private let backgroundView = UIView()
|
|
private let effectView = ShimmerEffectView()
|
|
private let maskImageView = UIImageView()
|
|
|
|
private var currentParams: (size: CGSize, theme: PresentationTheme, showFilters: Bool)?
|
|
|
|
public override init(frame: CGRect = .zero) {
|
|
super.init(frame: frame)
|
|
self.isUserInteractionEnabled = false
|
|
self.backgroundColor = .clear
|
|
self.layer.allowsGroupOpacity = true
|
|
|
|
self.addSubview(self.backgroundView)
|
|
self.addSubview(self.effectView)
|
|
self.addSubview(self.maskImageView)
|
|
}
|
|
|
|
required init?(coder: NSCoder) { fatalError() }
|
|
|
|
public func update(size: CGSize, theme: PresentationTheme, showFilters: Bool = false, isPlain: Bool = false, transition: ContainedViewLayoutTransition) {
|
|
let backgroundColor = isPlain ? theme.list.itemBlocksBackgroundColor : theme.list.blocksBackgroundColor
|
|
let color = theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85)
|
|
|
|
if self.currentParams?.size != size || self.currentParams?.theme !== theme || self.currentParams?.showFilters != showFilters {
|
|
self.currentParams = (size, theme, showFilters)
|
|
|
|
self.backgroundView.backgroundColor = color
|
|
|
|
self.maskImageView.image = generateImage(size, rotatedContext: { size, context in
|
|
context.setFillColor(backgroundColor.cgColor)
|
|
context.fill(CGRect(origin: .zero, size: size))
|
|
|
|
let sideInset: CGFloat = 16.0
|
|
|
|
if showFilters {
|
|
let filterSpacing: CGFloat = 6.0
|
|
let filterWidth = (size.width - sideInset * 2.0 - filterSpacing * 3.0) / 4.0
|
|
for i in 0 ..< 4 {
|
|
let rect = CGRect(origin: CGPoint(x: sideInset + (filterWidth + filterSpacing) * CGFloat(i), y: 0.0),
|
|
size: CGSize(width: filterWidth, height: 28.0))
|
|
context.addPath(CGPath(roundedRect: rect, cornerWidth: 14.0, cornerHeight: 14.0, transform: nil))
|
|
}
|
|
}
|
|
|
|
var currentY: CGFloat = 39.0 + 7.0
|
|
var rowIndex: Int = 0
|
|
|
|
let optionSpacing: CGFloat = 10.0
|
|
let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
|
let itemSize = CGSize(width: optionWidth, height: 154.0)
|
|
|
|
context.setBlendMode(.copy)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
|
|
while currentY < size.height {
|
|
for i in 0 ..< 3 {
|
|
let itemOrigin = CGPoint(
|
|
x: sideInset + CGFloat(i) * (itemSize.width + optionSpacing),
|
|
y: 39.0 + 9.0 + CGFloat(rowIndex) * (itemSize.height + optionSpacing)
|
|
)
|
|
context.addPath(CGPath(roundedRect: CGRect(origin: itemOrigin, size: itemSize),
|
|
cornerWidth: 10.0, cornerHeight: 10.0, transform: nil))
|
|
}
|
|
currentY += itemSize.height
|
|
rowIndex += 1
|
|
}
|
|
context.fillPath()
|
|
})
|
|
|
|
self.effectView.update(backgroundColor: color, foregroundColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4))
|
|
self.effectView.updateAbsoluteRect(CGRect(origin: .zero, size: size), within: size)
|
|
}
|
|
|
|
transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size))
|
|
transition.updateFrame(view: self.maskImageView, frame: CGRect(origin: .zero, size: size))
|
|
transition.updateFrame(view: self.effectView, frame: CGRect(origin: .zero, size: size))
|
|
}
|
|
}
|
|
|