mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Paid media improvements
This commit is contained in:
@@ -16,6 +16,7 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import ComponentFlow
|
||||
import ChatControllerInteraction
|
||||
import HierarchyTrackingLayer
|
||||
|
||||
public class ChatMessageUnlockMediaNode: ASDisplayNode {
|
||||
public class Arguments {
|
||||
@@ -63,11 +64,14 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode {
|
||||
private let contentNode: HighlightTrackingButtonNode
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private var textNode: TextNodeWithEntities?
|
||||
private var loadingView: LoadingEffectView?
|
||||
|
||||
private var pressed = { }
|
||||
|
||||
private var absolutePosition: (CGRect, CGSize)?
|
||||
|
||||
private var currentProgressDisposable: Disposable?
|
||||
|
||||
override public init() {
|
||||
self.contentNode = HighlightTrackingButtonNode()
|
||||
|
||||
@@ -83,10 +87,41 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode {
|
||||
self.contentNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.currentProgressDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
public func makeProgress() -> Promise<Bool> {
|
||||
let progress = Promise<Bool>()
|
||||
self.currentProgressDisposable?.dispose()
|
||||
self.currentProgressDisposable = (progress.get()
|
||||
|> distinctUntilChanged
|
||||
|> deliverOnMainQueue).start(next: { [weak self] hasProgress in
|
||||
guard let self, let loadingView = self.loadingView else {
|
||||
return
|
||||
}
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
if hasProgress {
|
||||
if loadingView.superview == nil {
|
||||
loadingView.alpha = 0.0
|
||||
self.view.addSubview(loadingView)
|
||||
transition.updateAlpha(layer: loadingView.layer, alpha: 1.0)
|
||||
}
|
||||
} else if loadingView.superview != nil {
|
||||
transition.updateAlpha(layer: loadingView.layer, alpha: 0.0, beginWithCurrentState: true, completion: { finished in
|
||||
if finished {
|
||||
loadingView.removeFromSuperview()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return progress
|
||||
}
|
||||
|
||||
public class func asyncLayout(_ maybeNode: ChatMessageUnlockMediaNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode) {
|
||||
let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode)
|
||||
|
||||
@@ -139,8 +174,171 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode {
|
||||
node.backgroundNode.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate)
|
||||
node.contentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
let loadingView: LoadingEffectView
|
||||
if let current = node.loadingView {
|
||||
loadingView = current
|
||||
} else {
|
||||
loadingView = LoadingEffectView()
|
||||
node.loadingView = loadingView
|
||||
}
|
||||
loadingView.frame = CGRect(origin: .zero, size: size)
|
||||
loadingView.update(color: UIColor.white, rect: CGRect(origin: .zero, size: size))
|
||||
|
||||
return node
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let shadowImage: UIImage? = {
|
||||
UIImage(named: "Stories/PanelGradient")
|
||||
}()
|
||||
|
||||
public final class LoadingEffectView: 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 var duration: Double
|
||||
private var gradientWidth: CGFloat
|
||||
|
||||
private var inHierarchy = false
|
||||
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.isInHierarchyUpdated = { [weak self] inHierarchy in
|
||||
guard let self, let size = self.size else {
|
||||
return
|
||||
}
|
||||
self.inHierarchy = inHierarchy
|
||||
self.updateAnimations(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func updateAnimations(size: CGSize) {
|
||||
if self.inHierarchy {
|
||||
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")
|
||||
} else {
|
||||
self.backgroundView.layer.removeAllAnimations()
|
||||
self.borderBackgroundView.layer.removeAllAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
public func update(color: UIColor, rect: CGRect) {
|
||||
let maskFrame = CGRect(origin: CGPoint(), size: rect.size).insetBy(dx: -4.0, dy: -4.0)
|
||||
|
||||
self.gradientWidth = 260.0
|
||||
self.duration = 1.2
|
||||
|
||||
self.maskContentsView.backgroundColor = .clear
|
||||
self.maskBorderContentsView.backgroundColor = color.withAlphaComponent(0.12)
|
||||
|
||||
// self.backgroundView.alpha = 0.25
|
||||
self.backgroundView.tintColor = color
|
||||
|
||||
self.borderBackgroundView.tintColor = color
|
||||
|
||||
self.maskContentsView.frame = maskFrame
|
||||
self.maskBorderContentsView.frame = maskFrame
|
||||
|
||||
let rectsSet: [CGRect] = [rect]
|
||||
|
||||
self.maskHighlightNode.outerRadius = rect.height / 2.0
|
||||
self.maskHighlightNode.updateRects(rectsSet)
|
||||
self.maskHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
|
||||
|
||||
self.maskBorderHighlightNode.outerRadius = rect.height / 2.0
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user