Add QR scanner opening animation

This commit is contained in:
Ilya Laktyushin
2023-01-02 23:19:51 +04:00
parent 2c7f754b2f
commit 775e93196a
9 changed files with 155 additions and 945 deletions

View File

@@ -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() {