mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Various improvements
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
final class FrameView: UIView {
|
||||
enum State {
|
||||
case viewFinder
|
||||
case segments(Set<Int>)
|
||||
case success
|
||||
case failure
|
||||
}
|
||||
|
||||
private let maskLayer = CALayer()
|
||||
|
||||
private let viewFinderLayer = ViewFinderLayer()
|
||||
private let transitionLayer = TransitionLayer()
|
||||
private let segmentsLayer = SegmentsLayer()
|
||||
|
||||
private var currentState: State = .viewFinder
|
||||
private var scheduledState: State?
|
||||
|
||||
private var isTransitioning = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundColor = .clear
|
||||
|
||||
//self.layer.mask = self.maskLayer
|
||||
|
||||
self.transitionLayer.isHidden = true
|
||||
self.segmentsLayer.isHidden = true
|
||||
|
||||
self.layer.addSublayer(self.viewFinderLayer)
|
||||
self.layer.addSublayer(self.transitionLayer)
|
||||
self.layer.addSublayer(self.segmentsLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(state: State, transition: ComponentTransition) {
|
||||
guard !self.isTransitioning else {
|
||||
self.scheduledState = state
|
||||
return
|
||||
}
|
||||
|
||||
let previousState = self.currentState
|
||||
self.currentState = state
|
||||
|
||||
switch state {
|
||||
case .viewFinder:
|
||||
switch previousState {
|
||||
case .viewFinder:
|
||||
break
|
||||
case .segments:
|
||||
self.isTransitioning = true
|
||||
self.segmentsLayer.animateOut(transition: transition) {
|
||||
self.segmentsLayer.isHidden = true
|
||||
self.transitionLayer.isHidden = false
|
||||
self.transitionLayer.animateOut(transition: transition) {
|
||||
self.transitionLayer.isHidden = true
|
||||
self.viewFinderLayer.isHidden = false
|
||||
self.viewFinderLayer.animateIn(transition: transition) {
|
||||
self.isTransitioning = false
|
||||
self.maybeApplyScheduledState()
|
||||
}
|
||||
}
|
||||
}
|
||||
case .success:
|
||||
break
|
||||
case .failure:
|
||||
break
|
||||
}
|
||||
case let .segments(segments):
|
||||
switch previousState {
|
||||
case .viewFinder:
|
||||
self.isTransitioning = true
|
||||
self.viewFinderLayer.animateOut(transition: transition) {
|
||||
self.viewFinderLayer.isHidden = true
|
||||
self.transitionLayer.isHidden = false
|
||||
self.transitionLayer.animateIn(transition: transition) {
|
||||
self.transitionLayer.isHidden = true
|
||||
self.segmentsLayer.isHidden = false
|
||||
self.segmentsLayer.animateIn (transition: transition) {
|
||||
self.isTransitioning = false
|
||||
self.maybeApplyScheduledState()
|
||||
}
|
||||
}
|
||||
}
|
||||
case .segments:
|
||||
self.segmentsLayer.update(segments: segments, transition: transition)
|
||||
case .success:
|
||||
break
|
||||
case .failure:
|
||||
break
|
||||
}
|
||||
case .success:
|
||||
self.isTransitioning = true
|
||||
self.segmentsLayer.animateOut(transition: transition) {
|
||||
self.segmentsLayer.isHidden = true
|
||||
self.transitionLayer.isHidden = false
|
||||
self.transitionLayer.update(color: UIColor(rgb: 0x65c466))
|
||||
self.transitionLayer.animateOut(transition: transition) {
|
||||
self.isTransitioning = false
|
||||
self.maybeApplyScheduledState()
|
||||
}
|
||||
}
|
||||
case .failure:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func maybeApplyScheduledState() {
|
||||
if !self.isTransitioning, let state = self.scheduledState {
|
||||
self.scheduledState = nil
|
||||
self.update(state: state, transition: .spring(duration: 0.3))
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
|
||||
self.maskLayer.frame = bounds
|
||||
|
||||
//let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
//let viewFinderWidth = bounds.width - 34.0
|
||||
//let viewFinderSize = CGSize(width: viewFinderWidth, height: floor(viewFinderWidth * 1.17778))
|
||||
|
||||
let viewFinderFrame = bounds.insetBy(dx: 29.0, dy: 29.0) //viewFinderSize.centered(around: center)
|
||||
self.viewFinderLayer.update(size: viewFinderFrame.size, closed: false, transition: .immediate)
|
||||
self.viewFinderLayer.frame = viewFinderFrame
|
||||
|
||||
let transitionFrame = bounds.insetBy(dx: 29.0, dy: 29.0) //viewFinderSize.centered(around: center)
|
||||
self.transitionLayer.update(size: transitionFrame.size)
|
||||
self.transitionLayer.frame = transitionFrame
|
||||
|
||||
let segmentsFrame = bounds.insetBy(dx: 15.0, dy: 15.0)
|
||||
self.segmentsLayer.update(size: segmentsFrame.size)
|
||||
self.segmentsLayer.frame = segmentsFrame
|
||||
}
|
||||
}
|
||||
|
||||
private let numberOfSegments = 64
|
||||
private let lineWidth: CGFloat = 4.0
|
||||
|
||||
final class ViewFinderLayer: SimpleLayer {
|
||||
private let viewFinderTopLeftLine = SimpleShapeLayer()
|
||||
private let viewFinderTopRightLine = SimpleShapeLayer()
|
||||
private let viewFinderBottomLeftLine = SimpleShapeLayer()
|
||||
private let viewFinderBottomRightLine = SimpleShapeLayer()
|
||||
|
||||
private var viewFinderLines: [SimpleShapeLayer] {
|
||||
return [
|
||||
self.viewFinderTopLeftLine,
|
||||
self.viewFinderTopRightLine,
|
||||
self.viewFinderBottomLeftLine,
|
||||
self.viewFinderBottomRightLine
|
||||
]
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
for line in self.viewFinderLines {
|
||||
line.strokeColor = UIColor.white.cgColor
|
||||
line.fillColor = UIColor.clear.cgColor
|
||||
line.lineWidth = lineWidth
|
||||
line.lineCap = .round
|
||||
self.addSublayer(line)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
func animateOut(transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
guard let size = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.update(size: size, closed: true, transition: transition, completion: completion)
|
||||
}
|
||||
|
||||
func animateIn(transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
guard let size = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.update(size: size, closed: false, transition: transition, completion: completion)
|
||||
}
|
||||
|
||||
func update(size: CGSize, closed: Bool, transition: ComponentTransition, completion: (() -> Void)? = nil) {
|
||||
self.validLayout = size
|
||||
|
||||
let cornerRadius = closed ? size.width / 2.0 : 18.0
|
||||
|
||||
let lineLength = size.width / 2.0 - cornerRadius
|
||||
let targetLineLength = 34.0
|
||||
let fraction = targetLineLength / lineLength
|
||||
let strokeFraction = (1.0 - fraction) / 2.0
|
||||
let strokeStart = closed ? 0.0 : strokeFraction
|
||||
let strokeEnd = closed ? 1.0 : 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))
|
||||
|
||||
transition.setShapeLayerPath(layer: self.viewFinderTopLeftLine, path: topLeftPath, completion: { _ in
|
||||
completion?()
|
||||
})
|
||||
transition.setShapeLayerStrokeStart(layer: self.viewFinderTopLeftLine, strokeStart: strokeStart)
|
||||
transition.setShapeLayerStrokeEnd(layer: self.viewFinderTopLeftLine, 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))
|
||||
|
||||
transition.setShapeLayerPath(layer: self.viewFinderTopRightLine, path: topRightPath)
|
||||
transition.setShapeLayerStrokeStart(layer: self.viewFinderTopRightLine, strokeStart: strokeStart)
|
||||
transition.setShapeLayerStrokeEnd(layer: self.viewFinderTopRightLine, 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))
|
||||
|
||||
transition.setShapeLayerPath(layer: self.viewFinderBottomRightLine, path: bottomRightPath)
|
||||
transition.setShapeLayerStrokeStart(layer: self.viewFinderBottomRightLine, strokeStart: strokeStart)
|
||||
transition.setShapeLayerStrokeEnd(layer: self.viewFinderBottomRightLine, 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))
|
||||
|
||||
transition.setShapeLayerPath(layer: self.viewFinderBottomLeftLine, path: bottomLeftPath)
|
||||
transition.setShapeLayerStrokeStart(layer: self.viewFinderBottomLeftLine, strokeStart: strokeStart)
|
||||
transition.setShapeLayerStrokeEnd(layer: self.viewFinderBottomLeftLine, strokeEnd: strokeEnd)
|
||||
|
||||
for line in self.viewFinderLines {
|
||||
line.frame = CGRect(origin: .zero, size: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
final class TransitionLayer: SimpleLayer {
|
||||
private var segmentLayers: [SimpleShapeLayer] = []
|
||||
|
||||
func animateIn(transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
var i = 0
|
||||
for layer in self.segmentLayers {
|
||||
transition.setShapeLayerStrokeStart(layer: layer, strokeStart: 0.499)
|
||||
transition.setShapeLayerStrokeEnd(layer: layer, strokeEnd: 0.501, completion: i == 0 ? { _ in completion() } : nil)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
var i = 0
|
||||
for layer in self.segmentLayers {
|
||||
transition.setShapeLayerStrokeStart(layer: layer, strokeStart: 0.0)
|
||||
transition.setShapeLayerStrokeEnd(layer: layer, strokeEnd: 1.0, completion: i == 0 ? { _ in completion() } : nil)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
func setupIfNeeded(size: CGSize) {
|
||||
guard self.segmentLayers.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
let radius: CGFloat = size.width / 2.0
|
||||
let gapInDegrees: CGFloat = 0.0
|
||||
let gapInRadians: CGFloat = gapInDegrees * .pi / 180.0
|
||||
|
||||
let totalGapAngle = CGFloat(numberOfSegments) * gapInRadians
|
||||
let totalSegmentAngle = 2 * .pi - totalGapAngle
|
||||
let segmentAngle = totalSegmentAngle / CGFloat(numberOfSegments)
|
||||
|
||||
for i in 0 ..< numberOfSegments {
|
||||
let startAngle = -segmentAngle * 0.5 + (CGFloat(i) * (segmentAngle + gapInRadians)) - .pi / 2
|
||||
let endAngle = startAngle + segmentAngle
|
||||
|
||||
let path = UIBezierPath(arcCenter: center,
|
||||
radius: radius,
|
||||
startAngle: startAngle,
|
||||
endAngle: endAngle,
|
||||
clockwise: true)
|
||||
|
||||
let stripeLayer = SimpleShapeLayer()
|
||||
stripeLayer.path = path.cgPath
|
||||
stripeLayer.strokeColor = UIColor(rgb: 0xaaaaaa).cgColor
|
||||
stripeLayer.lineWidth = lineWidth
|
||||
stripeLayer.fillColor = UIColor.clear.cgColor
|
||||
stripeLayer.lineCap = .round
|
||||
|
||||
self.addSublayer(stripeLayer)
|
||||
self.segmentLayers.append(stripeLayer)
|
||||
}
|
||||
}
|
||||
|
||||
func update(color: UIColor) {
|
||||
for layer in self.segmentLayers {
|
||||
layer.strokeColor = color.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
self.setupIfNeeded(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
final class SegmentsLayer: SimpleLayer {
|
||||
private var segmentLayers: [SimpleShapeLayer] = []
|
||||
|
||||
func animateIn(transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
var i = 0
|
||||
for layer in self.segmentLayers {
|
||||
transition.setShapeLayerStrokeStart(layer: layer, strokeStart: 0.0)
|
||||
transition.setShapeLayerStrokeEnd(layer: layer, strokeEnd: 0.32, completion: i == 0 ? { _ in completion() } : nil)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(transition: ComponentTransition, completion: @escaping () -> Void) {
|
||||
var i = 0
|
||||
for layer in self.segmentLayers {
|
||||
transition.setShapeLayerStrokeStart(layer: layer, strokeStart: 0.0)
|
||||
transition.setShapeLayerStrokeEnd(layer: layer, strokeEnd: 0.001, completion: i == 0 ? { _ in completion() } : nil)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
func setupIfNeeded(size: CGSize) {
|
||||
guard self.segmentLayers.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
let innerRadius: CGFloat = size.width / 2.0 - 13.0
|
||||
let outerRadius: CGFloat = size.width / 2.0 + 13.0
|
||||
let gapInDegrees: CGFloat = 2.0
|
||||
let gapInRadians: CGFloat = gapInDegrees * .pi / 180.0
|
||||
|
||||
let totalGapAngle = CGFloat(numberOfSegments) * gapInRadians
|
||||
let totalSegmentAngle = 2 * .pi - totalGapAngle
|
||||
let segmentAngle = totalSegmentAngle / CGFloat(numberOfSegments)
|
||||
|
||||
for i in 0 ..< numberOfSegments {
|
||||
let angle = (CGFloat(i) * (segmentAngle + gapInRadians)) - .pi / 2
|
||||
|
||||
let startPoint = CGPoint(
|
||||
x: center.x + innerRadius * cos(angle),
|
||||
y: center.y + innerRadius * sin(angle)
|
||||
)
|
||||
|
||||
let endPoint = CGPoint(
|
||||
x: center.x + outerRadius * cos(angle),
|
||||
y: center.y + outerRadius * sin(angle)
|
||||
)
|
||||
|
||||
let path = UIBezierPath()
|
||||
path.move(to: startPoint)
|
||||
path.addLine(to: endPoint)
|
||||
|
||||
let stripeLayer = SimpleShapeLayer()
|
||||
stripeLayer.path = path.cgPath
|
||||
stripeLayer.strokeColor = UIColor.white.cgColor
|
||||
stripeLayer.lineWidth = lineWidth
|
||||
stripeLayer.fillColor = UIColor.clear.cgColor
|
||||
stripeLayer.lineCap = .round
|
||||
stripeLayer.strokeStart = 0.0
|
||||
stripeLayer.strokeEnd = 0.001
|
||||
|
||||
self.addSublayer(stripeLayer)
|
||||
self.segmentLayers.append(stripeLayer)
|
||||
}
|
||||
}
|
||||
|
||||
func update(segments: Set<Int>, transition: ComponentTransition) {
|
||||
var mappedSegments = Set<Int>()
|
||||
for value in segments {
|
||||
for i in 0 ..< 4 {
|
||||
mappedSegments.insert(value * 4 + i)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 ..< numberOfSegments {
|
||||
let stripeLayer = self.segmentLayers[i]
|
||||
if mappedSegments.contains(i) {
|
||||
transition.setShapeLayerStrokeEnd(layer: stripeLayer, strokeEnd: 1.0)
|
||||
transition.setShapeLayerStrokeColor(layer: stripeLayer, color: UIColor(rgb: 0x00ca48))
|
||||
} else {
|
||||
transition.setShapeLayerStrokeEnd(layer: stripeLayer, strokeEnd: 0.32)
|
||||
transition.setShapeLayerStrokeColor(layer: stripeLayer, color: UIColor(rgb: 0xaaaaaa))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
self.setupIfNeeded(size: size)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user