mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
837 lines
36 KiB
Swift
837 lines
36 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import HierarchyTrackingLayer
|
|
import Postbox
|
|
import TelegramCore
|
|
import AnimationCache
|
|
import MultiAnimationRenderer
|
|
import SwiftSignalKit
|
|
import AccountContext
|
|
import EmojiTextAttachmentView
|
|
|
|
private let radius: CGFloat = 4.0
|
|
private let lineWidth: CGFloat = 3.0
|
|
|
|
private func addRoundedRectPath(context: CGContext, rect: CGRect, radius: CGFloat) {
|
|
context.saveGState()
|
|
context.translateBy(x: rect.minX, y: rect.minY)
|
|
context.scaleBy(x: radius, y: radius)
|
|
let fw = rect.width / radius
|
|
let fh = rect.height / radius
|
|
context.move(to: CGPoint(x: fw, y: fh / 2.0))
|
|
context.addArc(tangent1End: CGPoint(x: fw, y: fh), tangent2End: CGPoint(x: fw/2, y: fh), radius: 1.0)
|
|
context.addArc(tangent1End: CGPoint(x: 0, y: fh), tangent2End: CGPoint(x: 0, y: fh/2), radius: 1)
|
|
context.addArc(tangent1End: CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: fw/2, y: 0), radius: 1)
|
|
context.addArc(tangent1End: CGPoint(x: fw, y: 0), tangent2End: CGPoint(x: fw, y: fh/2), radius: 1)
|
|
context.closePath()
|
|
context.restoreGState()
|
|
}
|
|
|
|
private func generateBackgroundTemplateImage(addStripe: Bool, isTransparent: Bool) -> UIImage {
|
|
return generateImage(CGSize(width: radius * 2.0 + 4.0, height: radius * 2.0 + 8.0), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
|
|
addRoundedRectPath(context: context, rect: CGRect(origin: CGPoint(), size: size), radius: radius)
|
|
context.clip()
|
|
|
|
context.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor)
|
|
if !isTransparent {
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
}
|
|
|
|
if addStripe {
|
|
context.setFillColor(UIColor.white.withMultipliedAlpha(0.2).cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height)))
|
|
}
|
|
})!.stretchableImage(withLeftCapWidth: Int(radius) + 2, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate)
|
|
}
|
|
|
|
private func generateProgressTemplateImage() -> UIImage {
|
|
return generateImage(CGSize(width: radius * 2.0 + 4.0, height: radius * 2.0 + 8.0), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
|
|
addRoundedRectPath(context: context, rect: CGRect(origin: CGPoint(), size: size), radius: radius)
|
|
context.clip()
|
|
|
|
context.setFillColor(UIColor.white.withMultipliedAlpha(0.4).cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.setFillColor(UIColor.white.withMultipliedAlpha(0.7).cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height)))
|
|
|
|
context.resetClip()
|
|
|
|
let borderWidth: CGFloat = 1.5
|
|
addRoundedRectPath(context: context, rect: CGRect(origin: CGPoint(), size: size).insetBy(dx: borderWidth * 0.5, dy: borderWidth * 0.5), radius: radius)
|
|
context.setStrokeColor(UIColor.white.withAlphaComponent(0.7).cgColor)
|
|
context.strokePath()
|
|
|
|
})!.stretchableImage(withLeftCapWidth: Int(radius) + 2, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate)
|
|
}
|
|
|
|
private let backgroundSolidTemplateImage: UIImage = {
|
|
return generateBackgroundTemplateImage(addStripe: true, isTransparent: false)
|
|
}()
|
|
|
|
private let backgroundDashTemplateImage: UIImage = {
|
|
return generateBackgroundTemplateImage(addStripe: false, isTransparent: false)
|
|
}()
|
|
|
|
private let transparentBackgroundSolidTemplateImage: UIImage = {
|
|
return generateBackgroundTemplateImage(addStripe: true, isTransparent: true)
|
|
}()
|
|
|
|
private let transparentBackgroundDashTemplateImage: UIImage = {
|
|
return generateBackgroundTemplateImage(addStripe: false, isTransparent: true)
|
|
}()
|
|
|
|
private func generateDashBackgroundTemplateImage() -> UIImage {
|
|
return generateImage(CGSize(width: lineWidth, height: radius * 2.0 + 8.0), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: CGSize(width: radius * 2.0, height: size.height)), cornerRadius: radius).cgPath)
|
|
context.clip()
|
|
|
|
context.setFillColor(UIColor.white.withAlphaComponent(1.0).cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height)))
|
|
})!.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate)
|
|
}
|
|
|
|
private let dashBackgroundTemplateImage: UIImage = {
|
|
return generateDashBackgroundTemplateImage()
|
|
}()
|
|
|
|
private func generateDashTemplateImage(isMonochrome: Bool, isTriple: Bool) -> UIImage {
|
|
return generateImage(CGSize(width: radius * 2.0, height: 18.0), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(UIColor.white.cgColor)
|
|
|
|
let dashOffset: CGFloat
|
|
if isTriple {
|
|
dashOffset = isMonochrome ? -7.0 : 5.0
|
|
} else {
|
|
dashOffset = isMonochrome ? -4.0 : 5.0
|
|
}
|
|
|
|
let dashHeight: CGFloat = isTriple ? 6.0 : 9.0
|
|
|
|
context.translateBy(x: 0.0, y: dashOffset)
|
|
|
|
for _ in 0 ..< 2 {
|
|
context.move(to: CGPoint(x: 0.0, y: 3.0))
|
|
context.addLine(to: CGPoint(x: lineWidth, y: 0.0))
|
|
context.addLine(to: CGPoint(x: lineWidth, y: dashHeight))
|
|
context.addLine(to: CGPoint(x: 0.0, y: dashHeight + 3.0))
|
|
context.closePath()
|
|
context.fillPath()
|
|
|
|
context.translateBy(x: 0.0, y: size.height)
|
|
}
|
|
|
|
context.clear(CGRect(origin: CGPoint(x: lineWidth, y: 0.0), size: CGSize(width: size.width - lineWidth, height: size.height)))
|
|
})!.resizableImage(withCapInsets: .zero, resizingMode: .tile).withRenderingMode(.alwaysTemplate)
|
|
}
|
|
|
|
private let dashOpaqueTemplateImage: UIImage = {
|
|
return generateDashTemplateImage(isMonochrome: false, isTriple: false)
|
|
}()
|
|
|
|
private let dashOpaqueTripleTemplateImage: UIImage = {
|
|
return generateDashTemplateImage(isMonochrome: false, isTriple: true)
|
|
}()
|
|
|
|
private let dashMonochromeTemplateImage: UIImage = {
|
|
return generateDashTemplateImage(isMonochrome: true, isTriple: false)
|
|
}()
|
|
|
|
private let dashMonochromeTripleTemplateImage: UIImage = {
|
|
return generateDashTemplateImage(isMonochrome: true, isTriple: true)
|
|
}()
|
|
|
|
private func generateGradient(gradientWidth: CGFloat, baseAlpha: CGFloat) -> UIImage {
|
|
return generateImage(CGSize(width: 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 = UIImage(named: "Stories/PanelGradient") {
|
|
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)
|
|
}
|
|
|
|
private final class PatternContentsTarget: MultiAnimationRenderTarget {
|
|
private let imageUpdated: () -> Void
|
|
|
|
init(imageUpdated: @escaping () -> Void) {
|
|
self.imageUpdated = imageUpdated
|
|
|
|
super.init()
|
|
}
|
|
|
|
required init(coder: NSCoder) {
|
|
preconditionFailure()
|
|
}
|
|
|
|
override func transitionToContents(_ contents: AnyObject, didLoop: Bool) {
|
|
self.contents = contents
|
|
self.imageUpdated()
|
|
}
|
|
}
|
|
|
|
private final class LineView: UIView {
|
|
private let backgroundView: UIImageView
|
|
private var dashBackgroundView: UIImageView?
|
|
private var dashThirdBackgroundView: UIImageView?
|
|
|
|
private var params: Params?
|
|
private var isAnimating: Bool = false
|
|
|
|
private struct Params: Equatable {
|
|
var size: CGSize
|
|
var primaryColor: UIColor
|
|
var secondaryColor: UIColor?
|
|
var thirdColor: UIColor?
|
|
var displayProgress: Bool
|
|
|
|
init(size: CGSize, primaryColor: UIColor, secondaryColor: UIColor?, thirdColor: UIColor?, displayProgress: Bool) {
|
|
self.size = size
|
|
self.primaryColor = primaryColor
|
|
self.secondaryColor = secondaryColor
|
|
self.thirdColor = thirdColor
|
|
self.displayProgress = displayProgress
|
|
}
|
|
}
|
|
|
|
override init(frame: CGRect) {
|
|
self.backgroundView = UIImageView()
|
|
self.backgroundView.image = dashBackgroundTemplateImage
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.layer.cornerRadius = radius
|
|
if #available(iOS 13.0, *) {
|
|
self.layer.cornerCurve = .circular
|
|
}
|
|
|
|
self.addSubview(self.backgroundView)
|
|
}
|
|
|
|
required init(coder: NSCoder) {
|
|
preconditionFailure()
|
|
}
|
|
|
|
func updateAnimations() {
|
|
guard let params = self.params else {
|
|
return
|
|
}
|
|
|
|
if params.displayProgress {
|
|
if let dashBackgroundView = self.dashBackgroundView {
|
|
if dashBackgroundView.layer.animation(forKey: "progress") == nil {
|
|
let animation = dashBackgroundView.layer.makeAnimation(from: 18.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = 1.0
|
|
self.isAnimating = true
|
|
animation.completion = { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.isAnimating = false
|
|
self.updateAnimations()
|
|
}
|
|
dashBackgroundView.layer.add(animation, forKey: "progress")
|
|
}
|
|
if let dashThirdBackgroundView = self.dashThirdBackgroundView {
|
|
if dashThirdBackgroundView.layer.animation(forKey: "progress") == nil {
|
|
let animation = dashThirdBackgroundView.layer.makeAnimation(from: 18.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = 1.0
|
|
self.isAnimating = true
|
|
animation.completion = { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.isAnimating = false
|
|
self.updateAnimations()
|
|
}
|
|
dashThirdBackgroundView.layer.add(animation, forKey: "progress")
|
|
}
|
|
}
|
|
} else {
|
|
let phaseDuration: Double = 1.0
|
|
if self.backgroundView.layer.animation(forKey: "progress") == nil {
|
|
let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: -params.size.height as NSNumber, keyPath: "position.y", timingFunction: kCAMediaTimingFunctionSpring, duration: phaseDuration * 0.5, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: false, additive: true)
|
|
animation.repeatCount = 1.0
|
|
self.isAnimating = true
|
|
animation.completion = { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
|
|
let animation = self.backgroundView.layer.makeAnimation(from: params.size.height as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", timingFunction: kCAMediaTimingFunctionSpring, duration: phaseDuration * 0.5, delay: self.params?.displayProgress == true ? 0.1 : 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = 1.0
|
|
self.isAnimating = true
|
|
animation.completion = { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.isAnimating = false
|
|
self.updateAnimations()
|
|
}
|
|
self.backgroundView.layer.add(animation, forKey: "progress")
|
|
}
|
|
self.backgroundView.layer.add(animation, forKey: "progress")
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.isAnimating && self.dashBackgroundView == nil {
|
|
self.backgroundView.backgroundColor = params.primaryColor
|
|
self.backgroundView.layer.masksToBounds = true
|
|
self.backgroundView.layer.cornerRadius = radius * 0.5
|
|
} else {
|
|
self.backgroundView.backgroundColor = nil
|
|
self.backgroundView.layer.masksToBounds = false
|
|
}
|
|
|
|
self.layer.masksToBounds = params.secondaryColor != nil || self.isAnimating
|
|
}
|
|
|
|
func update(size: CGSize, primaryColor: UIColor, secondaryColor: UIColor?, thirdColor: UIColor?, displayProgress: Bool, animation: ListViewItemUpdateAnimation) {
|
|
let params = Params(
|
|
size: size,
|
|
primaryColor: primaryColor,
|
|
secondaryColor: secondaryColor,
|
|
thirdColor: thirdColor,
|
|
displayProgress: displayProgress
|
|
)
|
|
if self.params == params {
|
|
return
|
|
}
|
|
let previousParams = self.params
|
|
self.params = params
|
|
|
|
let _ = previousParams
|
|
|
|
|
|
self.backgroundView.tintColor = primaryColor
|
|
|
|
if let secondaryColor {
|
|
let dashBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: thirdColor != nil ? -12.0 : -18.0), size: CGSize(width: radius * 2.0, height: size.height + 18.0))
|
|
|
|
let dashBackgroundView: UIImageView
|
|
if let current = self.dashBackgroundView {
|
|
dashBackgroundView = current
|
|
|
|
animation.animator.updateFrame(layer: dashBackgroundView.layer, frame: dashBackgroundFrame, completion: nil)
|
|
} else {
|
|
dashBackgroundView = UIImageView()
|
|
self.dashBackgroundView = dashBackgroundView
|
|
self.addSubview(dashBackgroundView)
|
|
|
|
dashBackgroundView.frame = dashBackgroundFrame
|
|
}
|
|
|
|
let templateImage: UIImage
|
|
let monochromeTemplateImage: UIImage
|
|
|
|
if let thirdColor {
|
|
let thirdDashBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -18.0), size: CGSize(width: radius * 2.0, height: size.height + 18.0))
|
|
templateImage = dashOpaqueTripleTemplateImage
|
|
monochromeTemplateImage = dashMonochromeTripleTemplateImage
|
|
|
|
let dashThirdBackgroundView: UIImageView
|
|
if let current = self.dashThirdBackgroundView {
|
|
dashThirdBackgroundView = current
|
|
|
|
animation.animator.updateFrame(layer: dashThirdBackgroundView.layer, frame: thirdDashBackgroundFrame, completion: nil)
|
|
} else {
|
|
dashThirdBackgroundView = UIImageView()
|
|
self.dashThirdBackgroundView = dashThirdBackgroundView
|
|
self.addSubview(dashThirdBackgroundView)
|
|
|
|
dashThirdBackgroundView.frame = thirdDashBackgroundFrame
|
|
}
|
|
|
|
if thirdColor.alpha == 0.0 {
|
|
dashThirdBackgroundView.alpha = 0.4
|
|
dashThirdBackgroundView.image = monochromeTemplateImage
|
|
dashThirdBackgroundView.tintColor = primaryColor
|
|
} else {
|
|
dashThirdBackgroundView.alpha = 1.0
|
|
dashThirdBackgroundView.image = templateImage
|
|
dashThirdBackgroundView.tintColor = thirdColor
|
|
}
|
|
} else {
|
|
templateImage = dashOpaqueTemplateImage
|
|
monochromeTemplateImage = dashMonochromeTemplateImage
|
|
if let dashThirdBackgroundView = self.dashThirdBackgroundView {
|
|
self.dashThirdBackgroundView = nil
|
|
dashThirdBackgroundView.removeFromSuperview()
|
|
}
|
|
}
|
|
|
|
if secondaryColor.alpha == 0.0 {
|
|
self.backgroundView.alpha = 0.2
|
|
dashBackgroundView.image = monochromeTemplateImage
|
|
dashBackgroundView.tintColor = primaryColor
|
|
} else {
|
|
self.backgroundView.alpha = 1.0
|
|
dashBackgroundView.image = templateImage
|
|
dashBackgroundView.tintColor = secondaryColor
|
|
}
|
|
} else {
|
|
if let dashBackgroundView = self.dashBackgroundView {
|
|
self.dashBackgroundView = nil
|
|
dashBackgroundView.removeFromSuperview()
|
|
}
|
|
|
|
self.backgroundView.alpha = 1.0
|
|
}
|
|
|
|
self.layer.masksToBounds = params.secondaryColor != nil || self.isAnimating
|
|
|
|
animation.animator.updateFrame(layer: self.backgroundView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height)), completion: nil)
|
|
|
|
self.updateAnimations()
|
|
}
|
|
}
|
|
|
|
public final class MessageInlineBlockBackgroundView: UIView {
|
|
public final class Pattern: Equatable {
|
|
public let context: AccountContext
|
|
public let fileId: Int64
|
|
public let file: TelegramMediaFile?
|
|
|
|
public init(context: AccountContext, fileId: Int64, file: TelegramMediaFile?) {
|
|
self.context = context
|
|
self.fileId = fileId
|
|
self.file = file
|
|
}
|
|
|
|
public static func ==(lhs: Pattern, rhs: Pattern) -> Bool {
|
|
if lhs === rhs {
|
|
return true
|
|
}
|
|
if lhs.context !== rhs.context {
|
|
return false
|
|
}
|
|
if lhs.fileId != rhs.fileId {
|
|
return false
|
|
}
|
|
if lhs.file?.fileId != rhs.file?.fileId {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
private struct Params: Equatable {
|
|
var size: CGSize
|
|
var isTransparent: Bool
|
|
var primaryColor: UIColor
|
|
var secondaryColor: UIColor?
|
|
var thirdColor: UIColor?
|
|
var pattern: Pattern?
|
|
var displayProgress: Bool
|
|
|
|
init(
|
|
size: CGSize,
|
|
isTransparent: Bool,
|
|
primaryColor: UIColor,
|
|
secondaryColor: UIColor?,
|
|
thirdColor: UIColor?,
|
|
pattern: Pattern?,
|
|
displayProgress: Bool
|
|
) {
|
|
self.size = size
|
|
self.isTransparent = isTransparent
|
|
self.primaryColor = primaryColor
|
|
self.secondaryColor = secondaryColor
|
|
self.thirdColor = thirdColor
|
|
self.pattern = pattern
|
|
self.displayProgress = displayProgress
|
|
}
|
|
}
|
|
|
|
private var params: Params?
|
|
|
|
public var displayProgress: Bool = false {
|
|
didSet {
|
|
if self.displayProgress != oldValue {
|
|
if let params = self.params {
|
|
self.update(
|
|
size: params.size,
|
|
isTransparent: params.isTransparent,
|
|
primaryColor: params.primaryColor,
|
|
secondaryColor: params.secondaryColor,
|
|
thirdColor: params.thirdColor,
|
|
pattern: params.pattern,
|
|
animation: .None
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private let backgroundView: UIImageView
|
|
private var lineView: LineView
|
|
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
|
|
|
|
private var patternContentsTarget: PatternContentsTarget?
|
|
private var patternContentLayers: [SimpleLayer] = []
|
|
private var patternFile: TelegramMediaFile?
|
|
private var patternFileDisposable: Disposable?
|
|
private var patternImage: UIImage?
|
|
private var patternImageDisposable: Disposable?
|
|
|
|
private var progressBackgroundContentsView: UIImageView?
|
|
private var progressBackgroundMaskContainer: UIView?
|
|
private var progressBackgroundGradientView: UIImageView?
|
|
|
|
override public init(frame: CGRect) {
|
|
self.backgroundView = UIImageView()
|
|
self.lineView = LineView()
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.addSubview(self.backgroundView)
|
|
self.addSubview(self.lineView)
|
|
}
|
|
|
|
required public init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.patternFileDisposable?.dispose()
|
|
self.patternImageDisposable?.dispose()
|
|
}
|
|
|
|
private func updateAnimations() {
|
|
guard let hierarchyTrackingLayer = self.hierarchyTrackingLayer, hierarchyTrackingLayer.isInHierarchy else {
|
|
return
|
|
}
|
|
guard let params = self.params else {
|
|
return
|
|
}
|
|
guard let progressBackgroundGradientView = self.progressBackgroundGradientView else {
|
|
return
|
|
}
|
|
let gradientWidth = progressBackgroundGradientView.bounds.width
|
|
|
|
if progressBackgroundGradientView.layer.animation(forKey: "shimmer") != nil {
|
|
return
|
|
}
|
|
|
|
let duration: Double = 1.0
|
|
let animation = progressBackgroundGradientView.layer.makeAnimation(from: 0.0 as NSNumber, to: (params.size.width + gradientWidth + params.size.width * 0.1) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = Float.infinity
|
|
progressBackgroundGradientView.layer.add(animation, forKey: "shimmer")
|
|
|
|
self.lineView.updateAnimations()
|
|
}
|
|
|
|
private func loadPatternFromFile() {
|
|
guard let pattern = self.params?.pattern else {
|
|
return
|
|
}
|
|
guard let patternContentsTarget = self.patternContentsTarget else {
|
|
return
|
|
}
|
|
guard let patternFile = self.patternFile else {
|
|
return
|
|
}
|
|
self.patternImageDisposable = pattern.context.animationRenderer.loadFirstFrame(
|
|
target: patternContentsTarget,
|
|
cache: pattern.context.animationCache, itemId: "reply-pattern-\(patternFile.fileId)",
|
|
size: CGSize(width: 64, height: 64),
|
|
fetch: animationCacheFetchFile(
|
|
postbox: pattern.context.account.postbox,
|
|
userLocation: .other,
|
|
userContentType: .sticker,
|
|
resource: .media(media: .standalone(media: patternFile), resource: patternFile.resource),
|
|
type: AnimationCacheAnimationType(file: patternFile),
|
|
keyframeOnly: false,
|
|
customColor: .white
|
|
),
|
|
completion: { [weak self] _, _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.updatePatternLayerImages()
|
|
}
|
|
)
|
|
}
|
|
|
|
private func updatePatternLayerImages() {
|
|
let image = self.patternContentsTarget?.contents
|
|
for patternContentLayer in self.patternContentLayers {
|
|
patternContentLayer.contents = image
|
|
}
|
|
}
|
|
|
|
public func update(
|
|
size: CGSize,
|
|
isTransparent: Bool,
|
|
primaryColor: UIColor,
|
|
secondaryColor: UIColor?,
|
|
thirdColor: UIColor?,
|
|
pattern: Pattern?,
|
|
animation: ListViewItemUpdateAnimation
|
|
) {
|
|
let params = Params(
|
|
size: size,
|
|
isTransparent: isTransparent,
|
|
primaryColor: primaryColor,
|
|
secondaryColor: secondaryColor,
|
|
thirdColor: thirdColor,
|
|
pattern: pattern,
|
|
displayProgress: self.displayProgress
|
|
)
|
|
if self.params == params {
|
|
return
|
|
}
|
|
let previousParams = self.params
|
|
self.params = params
|
|
|
|
if previousParams?.primaryColor != params.primaryColor || previousParams?.secondaryColor != params.secondaryColor {
|
|
for patternContentLayer in self.patternContentLayers {
|
|
patternContentLayer.layerTintColor = primaryColor.cgColor
|
|
}
|
|
|
|
if params.isTransparent {
|
|
if params.secondaryColor != nil {
|
|
self.backgroundView.image = transparentBackgroundDashTemplateImage
|
|
} else {
|
|
self.backgroundView.image = transparentBackgroundSolidTemplateImage
|
|
}
|
|
} else {
|
|
if params.secondaryColor != nil {
|
|
self.backgroundView.image = backgroundDashTemplateImage
|
|
} else {
|
|
self.backgroundView.image = backgroundSolidTemplateImage
|
|
}
|
|
}
|
|
self.backgroundView.tintColor = params.primaryColor
|
|
}
|
|
|
|
if previousParams?.pattern != params.pattern {
|
|
if let pattern = params.pattern {
|
|
self.layer.masksToBounds = true
|
|
self.layer.cornerRadius = radius
|
|
if #available(iOS 13.0, *) {
|
|
self.layer.cornerCurve = .circular
|
|
}
|
|
|
|
if self.patternContentsTarget == nil {
|
|
self.patternContentsTarget = PatternContentsTarget(imageUpdated: { [weak self] in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.updatePatternLayerImages()
|
|
})
|
|
}
|
|
|
|
if previousParams?.pattern?.fileId != pattern.fileId {
|
|
self.patternFile = nil
|
|
self.patternFileDisposable?.dispose()
|
|
self.patternFileDisposable = nil
|
|
self.patternImageDisposable?.dispose()
|
|
|
|
if let file = pattern.file {
|
|
self.patternFile = file
|
|
self.loadPatternFromFile()
|
|
} else {
|
|
let fileId = pattern.fileId
|
|
self.patternFileDisposable = (pattern.context.engine.stickers.resolveInlineStickers(fileIds: [pattern.fileId])
|
|
|> deliverOnMainQueue).startStrict(next: { [weak self] files in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if let file = files[fileId] {
|
|
self.patternFile = file
|
|
self.loadPatternFromFile()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
} else {
|
|
self.layer.masksToBounds = false
|
|
|
|
self.patternContentsTarget = nil
|
|
self.patternFileDisposable?.dispose()
|
|
self.patternFileDisposable = nil
|
|
self.patternFile = nil
|
|
}
|
|
}
|
|
|
|
animation.animator.updateFrame(layer: self.backgroundView.layer, frame: CGRect(origin: CGPoint(), size: size), completion: nil)
|
|
|
|
let lineFrame = CGRect(origin: CGPoint(), size: CGSize(width: radius * 2.0, height: size.height))
|
|
self.lineView.update(
|
|
size: lineFrame.size,
|
|
primaryColor: params.primaryColor,
|
|
secondaryColor: params.secondaryColor,
|
|
thirdColor: params.thirdColor,
|
|
displayProgress: params.displayProgress,
|
|
animation: animation
|
|
)
|
|
animation.animator.updateFrame(layer: lineView.layer, frame: lineFrame, completion: nil)
|
|
|
|
if params.pattern != nil {
|
|
var maxIndex = 0
|
|
|
|
struct Placement {
|
|
var position: CGPoint
|
|
var size: CGFloat
|
|
|
|
init(_ position: CGPoint, _ size: CGFloat) {
|
|
self.position = position
|
|
self.size = size
|
|
}
|
|
}
|
|
|
|
let placements: [Placement] = [
|
|
Placement(CGPoint(x: 180.0, y: 13.0), 38.0),
|
|
Placement(CGPoint(x: 55.0, y: 47.0), 58.0),
|
|
Placement(CGPoint(x: 364.0, y: 26.0), 58.0),
|
|
Placement(CGPoint(x: 133.0, y: 74.0), 46.0),
|
|
Placement(CGPoint(x: 262.0, y: 67.0), 54.0),
|
|
Placement(CGPoint(x: 62.0, y: 125.0), 44.0),
|
|
Placement(CGPoint(x: 171.0, y: 135.0), 47.0),
|
|
Placement(CGPoint(x: 320.0, y: 124.0), 47.0),
|
|
]
|
|
|
|
for placement in placements {
|
|
let patternContentLayer: SimpleLayer
|
|
if maxIndex < self.patternContentLayers.count {
|
|
patternContentLayer = self.patternContentLayers[maxIndex]
|
|
} else {
|
|
patternContentLayer = SimpleLayer()
|
|
patternContentLayer.layerTintColor = primaryColor.cgColor
|
|
self.layer.addSublayer(patternContentLayer)
|
|
self.patternContentLayers.append(patternContentLayer)
|
|
}
|
|
patternContentLayer.contents = self.patternContentsTarget?.contents
|
|
|
|
let itemSize = CGSize(width: placement.size / 3.0, height: placement.size / 3.0)
|
|
patternContentLayer.frame = CGRect(origin: CGPoint(x: size.width - placement.position.x / 3.0 - itemSize.width * 0.5, y: placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize)
|
|
var alphaFraction = abs(placement.position.x) / min(500.0, size.width)
|
|
alphaFraction = min(1.0, max(0.0, alphaFraction))
|
|
patternContentLayer.opacity = 0.3 * Float(1.0 - alphaFraction)
|
|
|
|
maxIndex += 1
|
|
}
|
|
|
|
if maxIndex < self.patternContentLayers.count {
|
|
for i in maxIndex ..< self.patternContentLayers.count {
|
|
self.patternContentLayers[i].removeFromSuperlayer()
|
|
}
|
|
self.patternContentLayers.removeSubrange(maxIndex ..< self.patternContentLayers.count)
|
|
}
|
|
} else {
|
|
for patternContentLayer in self.patternContentLayers {
|
|
patternContentLayer.removeFromSuperlayer()
|
|
}
|
|
self.patternContentLayers.removeAll()
|
|
}
|
|
|
|
let gradientWidth: CGFloat = min(300.0, max(200.0, size.width * 0.9))
|
|
|
|
if previousParams?.displayProgress != params.displayProgress {
|
|
if params.displayProgress {
|
|
let progressBackgroundContentsView: UIImageView
|
|
if let current = self.progressBackgroundContentsView {
|
|
progressBackgroundContentsView = current
|
|
} else {
|
|
progressBackgroundContentsView = UIImageView()
|
|
progressBackgroundContentsView.image = generateProgressTemplateImage()
|
|
self.progressBackgroundContentsView = progressBackgroundContentsView
|
|
self.insertSubview(progressBackgroundContentsView, aboveSubview: self.backgroundView)
|
|
progressBackgroundContentsView.tintColor = primaryColor
|
|
}
|
|
|
|
let progressBackgroundMaskContainer: UIView
|
|
if let current = self.progressBackgroundMaskContainer {
|
|
progressBackgroundMaskContainer = current
|
|
} else {
|
|
progressBackgroundMaskContainer = UIView()
|
|
self.progressBackgroundMaskContainer = progressBackgroundMaskContainer
|
|
progressBackgroundContentsView.mask = progressBackgroundMaskContainer
|
|
}
|
|
|
|
let progressBackgroundGradientView: UIImageView
|
|
if let current = self.progressBackgroundGradientView {
|
|
progressBackgroundGradientView = current
|
|
} else {
|
|
progressBackgroundGradientView = UIImageView()
|
|
self.progressBackgroundGradientView = progressBackgroundGradientView
|
|
progressBackgroundMaskContainer.addSubview(progressBackgroundGradientView)
|
|
progressBackgroundGradientView.image = generateGradient(gradientWidth: 100.0, baseAlpha: 0.5)
|
|
}
|
|
|
|
progressBackgroundContentsView.frame = CGRect(origin: CGPoint(), size: size)
|
|
progressBackgroundMaskContainer.frame = CGRect(origin: CGPoint(), size: size)
|
|
progressBackgroundGradientView.frame = CGRect(origin: CGPoint(x: -gradientWidth, y: 0.0), size: CGSize(width: gradientWidth, height: size.height))
|
|
|
|
if self.hierarchyTrackingLayer == nil {
|
|
let hierarchyTrackingLayer = HierarchyTrackingLayer()
|
|
self.hierarchyTrackingLayer = hierarchyTrackingLayer
|
|
self.layer.addSublayer(hierarchyTrackingLayer)
|
|
hierarchyTrackingLayer.isInHierarchyUpdated = { [weak self] _ in
|
|
self?.updateAnimations()
|
|
}
|
|
}
|
|
} else {
|
|
if let progressBackgroundContentsView = self.progressBackgroundContentsView {
|
|
self.progressBackgroundContentsView = nil
|
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut)
|
|
transition.updateAlpha(layer: progressBackgroundContentsView.layer, alpha: 0.0, completion: { [weak progressBackgroundContentsView] _ in
|
|
progressBackgroundContentsView?.removeFromSuperview()
|
|
})
|
|
}
|
|
self.progressBackgroundMaskContainer = nil
|
|
self.progressBackgroundGradientView = nil
|
|
|
|
if let hierarchyTrackingLayer = self.hierarchyTrackingLayer {
|
|
self.hierarchyTrackingLayer = nil
|
|
hierarchyTrackingLayer.isInHierarchyUpdated = nil
|
|
hierarchyTrackingLayer.removeFromSuperlayer()
|
|
}
|
|
}
|
|
} else {
|
|
if let progressBackgroundContentsView = self.progressBackgroundContentsView {
|
|
animation.animator.updateFrame(layer: progressBackgroundContentsView.layer, frame: CGRect(origin: CGPoint(), size: size), completion: nil)
|
|
progressBackgroundContentsView.tintColor = primaryColor
|
|
}
|
|
if let progressBackgroundMaskContainer = self.progressBackgroundMaskContainer {
|
|
animation.animator.updateFrame(layer: progressBackgroundMaskContainer.layer, frame: CGRect(origin: CGPoint(), size: size), completion: nil)
|
|
}
|
|
if let progressBackgroundGradientView = self.progressBackgroundGradientView {
|
|
animation.animator.updateFrame(layer: progressBackgroundGradientView.layer, frame: CGRect(origin: CGPoint(x: -gradientWidth, y: 0.0), size: CGSize(width: gradientWidth, height: size.height)), completion: nil)
|
|
}
|
|
}
|
|
|
|
self.updateAnimations()
|
|
}
|
|
}
|