2024-12-22 17:16:14 +04:00

178 lines
8.0 KiB
Swift

import Foundation
import UIKit
import Display
import ComponentFlow
import Camera
final class CameraCodeFrameView: UIView {
private var cornerLayers: [SimpleShapeLayer] = []
private let cornerRadius: CGFloat = 12.0
private let focusedCornerRadius: CGFloat = 6.0
private let cornerShort: CGFloat = 16.0
private var currentSize: CGSize?
private var currentRect: CGRect?
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = false
for _ in 0..<4 {
let layer = SimpleShapeLayer()
layer.fillColor = UIColor.clear.cgColor
layer.strokeColor = UIColor.white.cgColor
layer.lineWidth = 2.0
layer.lineCap = .round
layer.lineJoin = .round
self.layer.addSublayer(layer)
self.cornerLayers.append(layer)
}
}
required init?(coder: NSCoder) {
preconditionFailure()
}
func update(size: CGSize, code: CameraCode?) {
let isFirstTime = self.currentSize == nil
self.currentSize = size
var duration: Double = 0.0
let bounds = CGRect(origin: .zero, size: size)
let rect: CGRect
if let code {
let codeRect = code.boundingBox
let side = max(codeRect.width * bounds.width, codeRect.height * bounds.height) * 0.7
let center = CGPoint(x: (1.0 - codeRect.center.y) * bounds.width, y: codeRect.center.x * bounds.height)
rect = CGSize(width: side, height: side).centered(around: center)
if !isFirstTime {
if let currentRect = self.currentRect {
if rect.center.distance(to: currentRect.center) > 40.0 || abs(rect.size.width - currentRect.size.width) > 40.0 {
duration = 0.35
} else {
duration = 0.2
}
} else {
duration = 0.4
}
}
self.currentRect = rect
} else {
rect = bounds.insetBy(dx: -2.0, dy: -2.0)
if !isFirstTime {
duration = 0.4
}
self.currentRect = nil
}
let focused = code != nil
self.applyPaths(to: self.cornerPaths(for: rect, focused: focused, rotation: 0.0), focused: focused, duration: duration)
}
private func cornerPaths(for rect: CGRect, focused: Bool, rotation: Double) -> [UIBezierPath] {
let effectiveCornerRadius = focused ? self.focusedCornerRadius : self.cornerRadius
let center = CGPoint(x: rect.midX, y: rect.midY)
let transform = CGAffineTransform(translationX: center.x, y: center.y).rotated(by: rotation).translatedBy(x: -center.x, y: -center.y)
let topLeftPath = UIBezierPath()
topLeftPath.move(to: CGPoint(x: rect.minX, y: focused ? rect.minY + self.cornerShort : rect.midY))
topLeftPath.addLine(to: CGPoint(x: rect.minX, y: rect.minY + effectiveCornerRadius))
topLeftPath.addQuadCurve(
to: CGPoint(x: rect.minX + effectiveCornerRadius, y: rect.minY),
controlPoint: CGPoint(x: rect.minX, y: rect.minY)
)
topLeftPath.addLine(to: CGPoint(x: focused ? rect.minX + self.cornerShort : rect.midX, y: rect.minY))
topLeftPath.apply(transform)
let topRightPath = UIBezierPath()
topRightPath.move(to: CGPoint(x: rect.maxX, y: focused ? rect.minY + self.cornerShort : rect.midY))
topRightPath.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + effectiveCornerRadius))
topRightPath.addQuadCurve(
to: CGPoint(x: rect.maxX - effectiveCornerRadius, y: rect.minY),
controlPoint: CGPoint(x: rect.maxX, y: rect.minY)
)
topRightPath.addLine(to: CGPoint(x: focused ? rect.maxX - self.cornerShort : rect.midX, y: rect.minY))
topRightPath.apply(transform)
let bottomRightPath = UIBezierPath()
bottomRightPath.move(to: CGPoint(x: rect.maxX, y: focused ? rect.maxY - self.cornerShort : rect.midY))
bottomRightPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - effectiveCornerRadius))
bottomRightPath.addQuadCurve(
to: CGPoint(x: rect.maxX - effectiveCornerRadius, y: rect.maxY),
controlPoint: CGPoint(x: rect.maxX, y: rect.maxY)
)
bottomRightPath.addLine(to: CGPoint(x: focused ? rect.maxX - self.cornerShort : rect.midX, y: rect.maxY))
bottomRightPath.apply(transform)
let bottomLeftPath = UIBezierPath()
bottomLeftPath.move(to: CGPoint(x: rect.minX, y: focused ? rect.maxY - self.cornerShort : rect.midY))
bottomLeftPath.addLine(to: CGPoint(x: rect.minX, y: rect.maxY - effectiveCornerRadius))
bottomLeftPath.addQuadCurve(
to: CGPoint(x: rect.minX + effectiveCornerRadius, y: rect.maxY),
controlPoint: CGPoint(x: rect.minX, y: rect.maxY)
)
bottomLeftPath.addLine(to: CGPoint(x: focused ? rect.minX + self.cornerShort : rect.midX, y: rect.maxY))
bottomLeftPath.apply(transform)
return [topLeftPath, topRightPath, bottomRightPath, bottomLeftPath]
}
private var animatingAppearance = false
private func applyPaths(to paths: [UIBezierPath], focused: Bool, duration: Double) {
let animatingAppearance = self.animatingAppearance
for (index, path) in paths.enumerated() {
let layer = self.cornerLayers[index]
let previousPath = layer.path
let previousAlpha = layer.opacity
let previousColor = layer.strokeColor ?? UIColor.clear.cgColor
let previousLineWidth = layer.lineWidth
if duration > 0.0 && !focused {
} else {
layer.path = path.cgPath
}
layer.opacity = focused ? 1.0 : 0.0
layer.strokeColor = focused ? UIColor(rgb: 0xf8d74a).cgColor : UIColor.white.cgColor
layer.lineWidth = focused ? 5.0 : 2.0
layer.shadowOffset = .zero
layer.shadowRadius = 1.0
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.2
if duration > 0.0 && !animatingAppearance {
if focused && previousAlpha.isZero && index == 0 {
self.animatingAppearance = true
}
if focused {
var currentPath = previousPath
var duration = duration
if let presentationPath = layer.presentation()?.path {
currentPath = presentationPath
duration *= 0.5
}
layer.animate(from: currentPath, to: path.cgPath, keyPath: "path", timingFunction: duration > 0.35 ? kCAMediaTimingFunctionSpring : CAMediaTimingFunctionName.linear.rawValue, duration: duration, completion: { _ in
if focused && index == 0 {
self.animatingAppearance = false
}
})
}
layer.animateAlpha(from: CGFloat(previousAlpha), to: CGFloat(layer.opacity), duration: focused ? 0.4 : 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: !focused ? { finished in
layer.path = path.cgPath
} : nil)
layer.animate(from: previousColor, to: layer.strokeColor ?? UIColor.white.cgColor, keyPath: "strokeColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3, delay: 0.15)
layer.animate(from: previousLineWidth, to: layer.lineWidth, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3)
}
}
}
}
private extension CGPoint {
func distance(to point: CGPoint) -> CGFloat {
return sqrt(pow((point.x - self.x), 2) + pow((point.y - self.y), 2))
}
}