mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Refactor BotPaymentsUI and LocalAuth
This commit is contained in:
259
submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift
Normal file
259
submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift
Normal file
@@ -0,0 +1,259 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import PassKit
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.semibold(17.0)
|
||||
|
||||
final class BotCheckoutActionButton: HighlightableButtonNode {
|
||||
static var diameter: CGFloat = 48.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?
|
||||
|
||||
init(inactiveFillColor: UIColor, activeFillColor: UIColor, foregroundColor: UIColor) {
|
||||
self.inactiveFillColor = inactiveFillColor
|
||||
self.activeFillColor = activeFillColor
|
||||
self.foregroundColor = foregroundColor
|
||||
|
||||
self.progressBackgroundNode = ASImageNode()
|
||||
self.progressBackgroundNode.displaysAsynchronously = false
|
||||
self.progressBackgroundNode.displayWithoutProcessing = true
|
||||
self.progressBackgroundNode.isLayerBacked = true
|
||||
self.progressBackgroundNode.image = generateImage(CGSize(width: BotCheckoutActionButton.diameter, height: BotCheckoutActionButton.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 = 10.0
|
||||
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: BotCheckoutActionButton.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
|
||||
self.activeBackgroundNode.isLayerBacked = true
|
||||
self.activeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: BotCheckoutActionButton.diameter, color: activeFillColor)
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.displaysAsynchronously = false
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.progressBackgroundNode)
|
||||
self.addSubnode(self.inactiveBackgroundNode)
|
||||
self.addSubnode(self.activeBackgroundNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
}
|
||||
|
||||
func setState(_ state: BotCheckoutActionButtonState) {
|
||||
if self.state != state {
|
||||
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 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 case .applePay = previousState {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = size
|
||||
|
||||
transition.updateFrame(node: self.progressBackgroundNode, frame: CGRect(origin: CGPoint(x: floor((size.width - BotCheckoutActionButton.diameter) / 2.0), y: 0.0), size: CGSize(width: BotCheckoutActionButton.diameter, height: BotCheckoutActionButton.diameter)))
|
||||
transition.updateFrame(node: self.inactiveBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter)))
|
||||
transition.updateFrame(node: self.activeBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter)))
|
||||
if let applePayButton = self.applePayButton {
|
||||
applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
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