mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Payment shimmer and more
This commit is contained in:
@@ -3,91 +3,37 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import PassKit
|
||||
import ShimmerEffect
|
||||
|
||||
enum BotCheckoutActionButtonState: Equatable {
|
||||
case loading
|
||||
case active(String)
|
||||
case inactive(String)
|
||||
case applePay
|
||||
|
||||
static func ==(lhs: BotCheckoutActionButtonState, rhs: BotCheckoutActionButtonState) -> Bool {
|
||||
switch lhs {
|
||||
case .loading:
|
||||
if case .loading = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .active(title):
|
||||
if case .active(title) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .inactive(title):
|
||||
if case .inactive(title) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .applePay:
|
||||
if case .applePay = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
case placeholder
|
||||
}
|
||||
|
||||
private let titleFont = Font.semibold(17.0)
|
||||
|
||||
final class BotCheckoutActionButton: HighlightableButtonNode {
|
||||
static var height: CGFloat = 52.0
|
||||
|
||||
private var inactiveFillColor: UIColor
|
||||
|
||||
private var activeFillColor: UIColor
|
||||
private var foregroundColor: UIColor
|
||||
|
||||
private let progressBackgroundNode: ASImageNode
|
||||
private let inactiveBackgroundNode: ASImageNode
|
||||
|
||||
private let activeBackgroundNode: ASImageNode
|
||||
private var applePayButton: UIButton?
|
||||
private let labelNode: TextNode
|
||||
|
||||
private var state: BotCheckoutActionButtonState?
|
||||
private var validLayout: CGSize?
|
||||
private var validLayout: (CGRect, CGSize)?
|
||||
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
|
||||
init(inactiveFillColor: UIColor, activeFillColor: UIColor, foregroundColor: UIColor) {
|
||||
self.inactiveFillColor = inactiveFillColor
|
||||
init(activeFillColor: UIColor, foregroundColor: UIColor) {
|
||||
self.activeFillColor = activeFillColor
|
||||
self.foregroundColor = foregroundColor
|
||||
|
||||
let diameter: CGFloat = 20.0
|
||||
|
||||
self.progressBackgroundNode = ASImageNode()
|
||||
self.progressBackgroundNode.displaysAsynchronously = false
|
||||
self.progressBackgroundNode.displayWithoutProcessing = true
|
||||
self.progressBackgroundNode.isLayerBacked = true
|
||||
self.progressBackgroundNode.image = generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
let strokeWidth: CGFloat = 2.0
|
||||
context.setFillColor(activeFillColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(inactiveFillColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: strokeWidth, y: strokeWidth), size: CGSize(width: size.width - strokeWidth * 2.0, height: size.height - strokeWidth * 2.0)))
|
||||
let cutout: CGFloat = diameter
|
||||
context.fill(CGRect(origin: CGPoint(x: floor((size.width - cutout) / 2.0), y: 0.0), size: CGSize(width: cutout, height: cutout)))
|
||||
})
|
||||
|
||||
self.inactiveBackgroundNode = ASImageNode()
|
||||
self.inactiveBackgroundNode.displaysAsynchronously = false
|
||||
self.inactiveBackgroundNode.displayWithoutProcessing = true
|
||||
self.inactiveBackgroundNode.isLayerBacked = true
|
||||
self.inactiveBackgroundNode.image = generateStretchableFilledCircleImage(diameter: diameter, color: self.foregroundColor, strokeColor: activeFillColor, strokeWidth: 2.0)
|
||||
self.inactiveBackgroundNode.alpha = 0.0
|
||||
|
||||
self.activeBackgroundNode = ASImageNode()
|
||||
self.activeBackgroundNode.displaysAsynchronously = false
|
||||
self.activeBackgroundNode.displayWithoutProcessing = true
|
||||
@@ -99,9 +45,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.progressBackgroundNode)
|
||||
self.addSubnode(self.inactiveBackgroundNode)
|
||||
|
||||
self.addSubnode(self.activeBackgroundNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
}
|
||||
@@ -111,136 +55,8 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
|
||||
let previousState = self.state
|
||||
self.state = state
|
||||
|
||||
if let validLayout = self.validLayout, let previousState = previousState {
|
||||
switch state {
|
||||
case .loading:
|
||||
self.inactiveBackgroundNode.layer.animateFrame(from: self.inactiveBackgroundNode.frame, to: self.progressBackgroundNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
if !self.inactiveBackgroundNode.alpha.isZero {
|
||||
self.inactiveBackgroundNode.alpha = 0.0
|
||||
self.inactiveBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||
}
|
||||
self.activeBackgroundNode.layer.animateFrame(from: self.activeBackgroundNode.frame, to: self.progressBackgroundNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.activeBackgroundNode.alpha = 0.0
|
||||
self.activeBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||
self.labelNode.alpha = 0.0
|
||||
self.labelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||
|
||||
self.progressBackgroundNode.alpha = 1.0
|
||||
self.progressBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
|
||||
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
basicAnimation.duration = 0.8
|
||||
basicAnimation.fromValue = NSNumber(value: Float(0.0))
|
||||
basicAnimation.toValue = NSNumber(value: Float.pi * 2.0)
|
||||
basicAnimation.repeatCount = Float.infinity
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
|
||||
self.progressBackgroundNode.layer.add(basicAnimation, forKey: "progressRotation")
|
||||
case let .active(title):
|
||||
if let applePayButton = self.applePayButton {
|
||||
self.applePayButton = nil
|
||||
applePayButton.removeFromSuperview()
|
||||
}
|
||||
|
||||
if case .active = previousState {
|
||||
let makeLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: validLayout, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
self.labelNode.frame = CGRect(origin: CGPoint(x: floor((validLayout.width - labelLayout.size.width) / 2.0), y: floor((validLayout.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||
let _ = labelApply()
|
||||
} else {
|
||||
self.inactiveBackgroundNode.layer.animateFrame(from: self.progressBackgroundNode.frame, to: self.activeBackgroundNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.inactiveBackgroundNode.alpha = 1.0
|
||||
self.progressBackgroundNode.alpha = 0.0
|
||||
|
||||
self.activeBackgroundNode.layer.animateFrame(from: self.progressBackgroundNode.frame, to: self.activeBackgroundNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.activeBackgroundNode.alpha = 1.0
|
||||
self.activeBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
let makeLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: validLayout, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
self.labelNode.frame = CGRect(origin: CGPoint(x: floor((validLayout.width - labelLayout.size.width) / 2.0), y: floor((validLayout.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||
let _ = labelApply()
|
||||
self.labelNode.alpha = 1.0
|
||||
self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
}
|
||||
case let .inactive(title):
|
||||
if case .inactive = previousState {
|
||||
let makeLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.activeFillColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: validLayout, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
self.labelNode.frame = CGRect(origin: CGPoint(x: floor((validLayout.width - labelLayout.size.width) / 2.0), y: floor((validLayout.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||
let _ = labelApply()
|
||||
} else {
|
||||
self.inactiveBackgroundNode.layer.animateFrame(from: self.inactiveBackgroundNode.frame, to: self.activeBackgroundNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.inactiveBackgroundNode.alpha = 1.0
|
||||
self.progressBackgroundNode.alpha = 0.0
|
||||
|
||||
self.activeBackgroundNode.alpha = 0.0
|
||||
|
||||
let makeLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: validLayout, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
self.labelNode.frame = CGRect(origin: CGPoint(x: floor((validLayout.width - labelLayout.size.width) / 2.0), y: floor((validLayout.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||
let _ = labelApply()
|
||||
self.labelNode.alpha = 1.0
|
||||
self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
}
|
||||
case .applePay:
|
||||
if self.applePayButton == nil {
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
let applePayButton: PKPaymentButton
|
||||
if #available(iOS 14.0, *) {
|
||||
applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
|
||||
} else {
|
||||
applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
|
||||
}
|
||||
applePayButton.addTarget(self, action: #selector(self.applePayButtonPressed), for: .touchUpInside)
|
||||
self.view.addSubview(applePayButton)
|
||||
self.applePayButton = applePayButton
|
||||
}
|
||||
}
|
||||
if let applePayButton = self.applePayButton {
|
||||
applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: validLayout.width, height: BotCheckoutActionButton.height))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch state {
|
||||
case .loading:
|
||||
self.labelNode.alpha = 0.0
|
||||
self.progressBackgroundNode.alpha = 1.0
|
||||
self.activeBackgroundNode.alpha = 0.0
|
||||
|
||||
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
basicAnimation.duration = 0.8
|
||||
basicAnimation.fromValue = NSNumber(value: Float(0.0))
|
||||
basicAnimation.toValue = NSNumber(value: Float.pi * 2.0)
|
||||
basicAnimation.repeatCount = Float.infinity
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
|
||||
self.progressBackgroundNode.layer.add(basicAnimation, forKey: "progressRotation")
|
||||
case .active:
|
||||
self.labelNode.alpha = 1.0
|
||||
self.progressBackgroundNode.alpha = 0.0
|
||||
self.inactiveBackgroundNode.alpha = 0.0
|
||||
self.activeBackgroundNode.alpha = 1.0
|
||||
case .inactive:
|
||||
self.labelNode.alpha = 1.0
|
||||
self.progressBackgroundNode.alpha = 0.0
|
||||
self.inactiveBackgroundNode.alpha = 1.0
|
||||
self.activeBackgroundNode.alpha = 0.0
|
||||
case .applePay:
|
||||
self.labelNode.alpha = 0.0
|
||||
self.progressBackgroundNode.alpha = 0.0
|
||||
self.inactiveBackgroundNode.alpha = 0.0
|
||||
self.activeBackgroundNode.alpha = 0.0
|
||||
if self.applePayButton == nil {
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
let applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
|
||||
self.view.addSubview(applePayButton)
|
||||
self.applePayButton = applePayButton
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (absoluteRect, containerSize) = self.validLayout, let previousState = previousState {
|
||||
self.updateLayout(absoluteRect: absoluteRect, containerSize: containerSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,33 +65,81 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
|
||||
self.sendActions(forControlEvents: .touchUpInside, with: nil)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = size
|
||||
|
||||
transition.updateFrame(node: self.progressBackgroundNode, frame: CGRect(origin: CGPoint(x: floor((size.width - BotCheckoutActionButton.height) / 2.0), y: 0.0), size: CGSize(width: BotCheckoutActionButton.height, height: BotCheckoutActionButton.height)))
|
||||
transition.updateFrame(node: self.inactiveBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height)))
|
||||
func updateLayout(absoluteRect: CGRect, containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
let size = absoluteRect.size
|
||||
|
||||
self.validLayout = (absoluteRect, containerSize)
|
||||
|
||||
transition.updateFrame(node: self.activeBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height)))
|
||||
if let applePayButton = self.applePayButton {
|
||||
applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height))
|
||||
}
|
||||
|
||||
var labelSize = self.labelNode.bounds.size
|
||||
if let state = self.state {
|
||||
switch state {
|
||||
case let .active(title):
|
||||
let makeLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: size, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let _ = labelApply()
|
||||
labelSize = labelLayout.size
|
||||
case let .inactive(title):
|
||||
let makeLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.activeFillColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: size, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let _ = labelApply()
|
||||
labelSize = labelLayout.size
|
||||
default:
|
||||
break
|
||||
case let .active(title):
|
||||
if let applePayButton = self.applePayButton {
|
||||
self.applePayButton = nil
|
||||
applePayButton.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
placeholderNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
let makeLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: size, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let _ = labelApply()
|
||||
labelSize = labelLayout.size
|
||||
case .applePay:
|
||||
if self.applePayButton == nil {
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
let applePayButton: PKPaymentButton
|
||||
if #available(iOS 14.0, *) {
|
||||
applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
|
||||
} else {
|
||||
applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
|
||||
}
|
||||
applePayButton.addTarget(self, action: #selector(self.applePayButtonPressed), for: .touchUpInside)
|
||||
self.view.addSubview(applePayButton)
|
||||
self.applePayButton = applePayButton
|
||||
}
|
||||
}
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
placeholderNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let applePayButton = self.applePayButton {
|
||||
applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height))
|
||||
}
|
||||
case .placeholder:
|
||||
if let applePayButton = self.applePayButton {
|
||||
self.applePayButton = nil
|
||||
applePayButton.removeFromSuperview()
|
||||
}
|
||||
|
||||
let contentSize = CGSize(width: 80.0, height: 8.0)
|
||||
|
||||
let shimmerNode: ShimmerEffectNode
|
||||
if let current = self.placeholderNode {
|
||||
shimmerNode = current
|
||||
} else {
|
||||
shimmerNode = ShimmerEffectNode()
|
||||
self.placeholderNode = shimmerNode
|
||||
self.addSubnode(shimmerNode)
|
||||
}
|
||||
shimmerNode.frame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) / 2.0), y: floor((size.height - contentSize.height) / 2.0)), size: contentSize)
|
||||
shimmerNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: absoluteRect.minX + shimmerNode.frame.minX, y: absoluteRect.minY + shimmerNode.frame.minY), size: contentSize), within: containerSize)
|
||||
|
||||
var shapes: [ShimmerEffectNode.Shape] = []
|
||||
|
||||
shapes.append(.roundedRectLine(startPoint: CGPoint(x: 0.0, y: 0.0), width: contentSize.width, diameter: contentSize.height))
|
||||
|
||||
shimmerNode.update(backgroundColor: self.activeFillColor, foregroundColor: self.activeFillColor.mixedWith(UIColor.white, alpha: 0.25), shimmeringColor: self.activeFillColor.mixedWith(UIColor.white, alpha: 0.15), shapes: shapes, size: contentSize)
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: floor((size.width - labelSize.width) / 2.0), y: floor((size.height - labelSize.height) / 2.0)), size: labelSize))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user