mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Add QR scanner opening animation
This commit is contained in:
@@ -291,6 +291,98 @@ public final class QrCodeScanScreen: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private final class FrameNode: ASDisplayNode {
|
||||
let topLeftLine: CAShapeLayer
|
||||
let topRightLine: CAShapeLayer
|
||||
let bottomLeftLine: CAShapeLayer
|
||||
let bottomRightLine: CAShapeLayer
|
||||
|
||||
override init() {
|
||||
self.topLeftLine = CAShapeLayer()
|
||||
self.topRightLine = CAShapeLayer()
|
||||
self.bottomLeftLine = CAShapeLayer()
|
||||
self.bottomRightLine = CAShapeLayer()
|
||||
|
||||
super.init()
|
||||
|
||||
for line in self.lines {
|
||||
line.strokeColor = UIColor.white.cgColor
|
||||
line.lineWidth = 4.0
|
||||
line.lineCap = .round
|
||||
self.layer.addSublayer(line)
|
||||
}
|
||||
}
|
||||
|
||||
private var lines: [CAShapeLayer] {
|
||||
return [
|
||||
self.topLeftLine,
|
||||
self.topRightLine,
|
||||
self.bottomLeftLine,
|
||||
self.bottomRightLine
|
||||
]
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
let strokeStart = self.topLeftLine.strokeStart
|
||||
let strokeEnd = self.topLeftLine.strokeEnd
|
||||
|
||||
let duration: Double = 0.85
|
||||
let delay: Double = 0.15
|
||||
|
||||
for line in self.lines {
|
||||
line.animateSpring(from: 0.0 as NSNumber, to: strokeStart as NSNumber, keyPath: "strokeStart", duration: duration, delay: delay)
|
||||
line.animateSpring(from: 1.0 as NSNumber, to: strokeEnd as NSNumber, keyPath: "strokeEnd", duration: duration, delay: delay)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
let cornerRadius: CGFloat = 6.0
|
||||
|
||||
let lineLength = size.width / 2.0 - cornerRadius
|
||||
let targetLineLength = 24.0
|
||||
let fraction = targetLineLength / lineLength
|
||||
let strokeFraction = (1.0 - fraction) / 2.0
|
||||
let strokeStart = strokeFraction
|
||||
let strokeEnd = 1.0 - strokeFraction
|
||||
|
||||
let topLeftPath = CGMutablePath()
|
||||
topLeftPath.move(to: CGPoint(x: 0.0, y: size.height / 2.0))
|
||||
topLeftPath.addArc(center: CGPoint(x: cornerRadius, y: cornerRadius), radius: cornerRadius, startAngle: -.pi, endAngle: -.pi / 2.0, clockwise: false)
|
||||
topLeftPath.addLine(to: CGPoint(x: size.width / 2.0, y: 0.0))
|
||||
self.topLeftLine.path = topLeftPath
|
||||
self.topLeftLine.strokeStart = strokeStart
|
||||
self.topLeftLine.strokeEnd = strokeEnd
|
||||
|
||||
let topRightPath = CGMutablePath()
|
||||
topRightPath.move(to: CGPoint(x: size.width / 2.0, y: 0.0))
|
||||
topRightPath.addArc(center: CGPoint(x: size.width - cornerRadius, y: cornerRadius), radius: cornerRadius, startAngle: -.pi / 2.0, endAngle: 0.0, clockwise: false)
|
||||
topRightPath.addLine(to: CGPoint(x: size.width, y: size.height / 2.0))
|
||||
self.topRightLine.path = topRightPath
|
||||
self.topRightLine.strokeStart = strokeStart
|
||||
self.topRightLine.strokeEnd = strokeEnd
|
||||
|
||||
let bottomRightPath = CGMutablePath()
|
||||
bottomRightPath.move(to: CGPoint(x: size.width, y: size.height / 2.0))
|
||||
bottomRightPath.addArc(center: CGPoint(x: size.width - cornerRadius, y: size.height - cornerRadius), radius: cornerRadius, startAngle: 0.0, endAngle: .pi / 2.0, clockwise: false)
|
||||
bottomRightPath.addLine(to: CGPoint(x: size.width / 2.0, y: size.height))
|
||||
self.bottomRightLine.path = bottomRightPath
|
||||
self.bottomRightLine.strokeStart = strokeStart
|
||||
self.bottomRightLine.strokeEnd = strokeEnd
|
||||
|
||||
let bottomLeftPath = CGMutablePath()
|
||||
bottomLeftPath.move(to: CGPoint(x: size.width / 2.0, y: size.height))
|
||||
bottomLeftPath.addArc(center: CGPoint(x: cornerRadius, y: size.height - cornerRadius), radius: cornerRadius, startAngle: .pi / 2.0, endAngle: .pi, clockwise: false)
|
||||
bottomLeftPath.addLine(to: CGPoint(x: 0.0, y: size.height / 2.0))
|
||||
self.bottomLeftLine.path = bottomLeftPath
|
||||
self.bottomLeftLine.strokeStart = strokeStart
|
||||
self.bottomLeftLine.strokeEnd = strokeEnd
|
||||
|
||||
for line in self.lines {
|
||||
line.frame = CGRect(origin: .zero, size: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
@@ -304,7 +396,7 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
||||
private let leftDimNode: ASDisplayNode
|
||||
private let rightDimNode: ASDisplayNode
|
||||
private let centerDimNode: ASDisplayNode
|
||||
private let frameNode: ASImageNode
|
||||
private let frameNode: FrameNode
|
||||
private let galleryButtonNode: GlassButtonNode
|
||||
private let torchButtonNode: GlassButtonNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
@@ -350,28 +442,29 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
||||
self.fadeNode.alpha = 0.0
|
||||
self.fadeNode.backgroundColor = .black
|
||||
|
||||
let dimColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||
|
||||
self.topDimNode = ASDisplayNode()
|
||||
self.topDimNode.alpha = 0.625
|
||||
self.topDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||
self.topDimNode.backgroundColor = dimColor
|
||||
|
||||
self.bottomDimNode = ASDisplayNode()
|
||||
self.bottomDimNode.alpha = 0.625
|
||||
self.bottomDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||
self.bottomDimNode.backgroundColor = dimColor
|
||||
|
||||
self.leftDimNode = ASDisplayNode()
|
||||
self.leftDimNode.alpha = 0.625
|
||||
self.leftDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||
self.leftDimNode.backgroundColor = dimColor
|
||||
|
||||
self.rightDimNode = ASDisplayNode()
|
||||
self.rightDimNode.alpha = 0.625
|
||||
self.rightDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||
self.rightDimNode.backgroundColor = dimColor
|
||||
|
||||
self.centerDimNode = ASDisplayNode()
|
||||
self.centerDimNode.alpha = 0.0
|
||||
self.centerDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||
self.centerDimNode.backgroundColor = dimColor
|
||||
|
||||
self.frameNode = ASImageNode()
|
||||
self.frameNode.image = generateFrameImage()
|
||||
self.frameNode = FrameNode()
|
||||
|
||||
self.galleryButtonNode = GlassButtonNode(icon: UIImage(bundleImageName: "Wallet/CameraGalleryIcon")!, label: nil)
|
||||
self.torchButtonNode = GlassButtonNode(icon: UIImage(bundleImageName: "Wallet/CameraFlashIcon")!, label: nil)
|
||||
@@ -517,6 +610,16 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
||||
self.textNode.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
private var animatedIn = false
|
||||
func animateIn() {
|
||||
guard !self.animatedIn else {
|
||||
return
|
||||
}
|
||||
self.animatedIn = true
|
||||
|
||||
self.frameNode.animateIn()
|
||||
}
|
||||
|
||||
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
@@ -551,9 +654,16 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
private var animatingIn = false
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, animateIn: Bool = false, transition: ContainedViewLayoutTransition) {
|
||||
let hadLayout = self.validLayout != nil
|
||||
self.validLayout = (layout, navigationHeight)
|
||||
|
||||
var prepareForAnimateIn = false
|
||||
if !hadLayout {
|
||||
prepareForAnimateIn = true
|
||||
}
|
||||
|
||||
let sideInset: CGFloat = 66.0
|
||||
let titleSpacing: CGFloat = 48.0
|
||||
let bounds = CGRect(origin: CGPoint(), size: layout.size)
|
||||
@@ -571,11 +681,20 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
||||
transition.updateFrame(node: self.fadeNode, frame: bounds)
|
||||
|
||||
let frameSide = max(240.0, layout.size.width - sideInset * 2.0)
|
||||
let animateInScale: CGFloat = 0.4
|
||||
var effectiveFrameSide = frameSide
|
||||
if prepareForAnimateIn {
|
||||
effectiveFrameSide = round(effectiveFrameSide * animateInScale)
|
||||
}
|
||||
|
||||
let dimHeight = ceil((layout.size.height - frameSide) / 2.0)
|
||||
let effectiveDimHeight = ceil((layout.size.height - effectiveFrameSide) / 2.0)
|
||||
let dimInset = (layout.size.width - frameSide) / 2.0
|
||||
let effectiveDimInset = (layout.size.width - effectiveFrameSide) / 2.0
|
||||
|
||||
let dimAlpha: CGFloat
|
||||
let dimRect: CGRect
|
||||
let frameRect: CGRect
|
||||
let controlsAlpha: CGFloat
|
||||
let centerDimAlpha: CGFloat = 0.0
|
||||
let frameAlpha: CGFloat = 1.0
|
||||
@@ -585,10 +704,12 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
||||
let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6
|
||||
let center = CGPoint(x: (1.0 - focusedRect.center.y) * bounds.width, y: focusedRect.center.x * bounds.height)
|
||||
dimRect = CGRect(x: center.x - side / 2.0, y: center.y - side / 2.0, width: side, height: side)
|
||||
frameRect = dimRect
|
||||
} else {
|
||||
controlsAlpha = 1.0
|
||||
dimAlpha = 0.625
|
||||
dimRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0)
|
||||
dimRect = CGRect(x: effectiveDimInset, y: effectiveDimHeight, width: layout.size.width - effectiveDimInset * 2.0, height: layout.size.height - effectiveDimHeight * 2.0)
|
||||
frameRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0)
|
||||
}
|
||||
|
||||
transition.updateAlpha(node: self.topDimNode, alpha: dimAlpha)
|
||||
@@ -598,12 +719,25 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
||||
transition.updateAlpha(node: self.centerDimNode, alpha: centerDimAlpha)
|
||||
transition.updateAlpha(node: self.frameNode, alpha: frameAlpha)
|
||||
|
||||
transition.updateFrame(node: self.topDimNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: dimRect.minY))
|
||||
transition.updateFrame(node: self.bottomDimNode, frame: CGRect(x: 0.0, y: dimRect.maxY, width: layout.size.width, height: max(0.0, layout.size.height - dimRect.maxY)))
|
||||
transition.updateFrame(node: self.leftDimNode, frame: CGRect(x: 0.0, y: dimRect.minY, width: max(0.0, dimRect.minX), height: dimRect.height))
|
||||
transition.updateFrame(node: self.rightDimNode, frame: CGRect(x: dimRect.maxX, y: dimRect.minY, width: max(0.0, layout.size.width - dimRect.maxX), height: dimRect.height))
|
||||
transition.updateFrame(node: self.frameNode, frame: dimRect.insetBy(dx: -2.0, dy: -2.0))
|
||||
transition.updateFrame(node: self.centerDimNode, frame: dimRect)
|
||||
if !self.animatingIn {
|
||||
var delay: Double = 0.0
|
||||
if animateIn {
|
||||
self.animatingIn = true
|
||||
delay = 0.1
|
||||
}
|
||||
transition.updateFrame(node: self.topDimNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: dimRect.minY), delay: delay, completion: { _ in
|
||||
self.animatingIn = false
|
||||
})
|
||||
transition.updateFrame(node: self.bottomDimNode, frame: CGRect(x: 0.0, y: dimRect.maxY, width: layout.size.width, height: max(0.0, layout.size.height - dimRect.maxY)), delay: delay)
|
||||
transition.updateFrame(node: self.leftDimNode, frame: CGRect(x: 0.0, y: dimRect.minY, width: max(0.0, dimRect.minX), height: dimRect.height), delay: delay)
|
||||
transition.updateFrame(node: self.rightDimNode, frame: CGRect(x: dimRect.maxX, y: dimRect.minY, width: max(0.0, layout.size.width - dimRect.maxX), height: dimRect.height), delay: delay)
|
||||
transition.updateFrame(node: self.frameNode, frame: frameRect)
|
||||
self.frameNode.updateLayout(size: frameRect.size)
|
||||
transition.updateFrame(node: self.centerDimNode, frame: frameRect)
|
||||
if animateIn {
|
||||
transition.animateTransformScale(node: self.frameNode, from: CGPoint(x: animateInScale, y: animateInScale), delay: delay)
|
||||
}
|
||||
}
|
||||
|
||||
let buttonSize = CGSize(width: 72.0, height: 72.0)
|
||||
var torchFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 98.0), size: buttonSize)
|
||||
@@ -657,6 +791,11 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
|
||||
self.highlightViews.append(view)
|
||||
}
|
||||
}
|
||||
|
||||
if prepareForAnimateIn {
|
||||
self.animateIn()
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, animateIn: true, transition: .animated(duration: 0.8, curve: .customSpring(damping: 88.0, initialVelocity: 0.0)))
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func galleryPressed() {
|
||||
|
||||
Reference in New Issue
Block a user