import Foundation import UIKit import Display import ComponentFlow public final class ProgressIndicatorComponent: Component { public let diameter: CGFloat public let value: Double public let backgroundColor: UIColor public let foregroundColor: UIColor public init( diameter: CGFloat, backgroundColor: UIColor, foregroundColor: UIColor, value: Double ) { self.diameter = diameter self.backgroundColor = backgroundColor self.foregroundColor = foregroundColor self.value = value } public static func ==(lhs: ProgressIndicatorComponent, rhs: ProgressIndicatorComponent) -> Bool { if lhs.diameter != rhs.diameter { return false } if lhs.backgroundColor != rhs.backgroundColor { return false } if lhs.foregroundColor != rhs.foregroundColor { return false } if lhs.value != rhs.value { return false } return true } public final class View: UIView { private var currentComponent: ProgressIndicatorComponent? private let foregroundShapeLayer: SimpleShapeLayer public init() { self.foregroundShapeLayer = SimpleShapeLayer() self.foregroundShapeLayer.isOpaque = false self.foregroundShapeLayer.backgroundColor = nil self.foregroundShapeLayer.fillColor = nil self.foregroundShapeLayer.lineCap = .round super.init(frame: CGRect()) let shapeLayer = self.layer as! CAShapeLayer shapeLayer.isOpaque = false shapeLayer.backgroundColor = nil shapeLayer.fillColor = nil self.layer.addSublayer(self.foregroundShapeLayer) } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override public static var layerClass: AnyClass { return CAShapeLayer.self } func update(component: ProgressIndicatorComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { let lineWidth: CGFloat = 1.33 let size = CGSize(width: component.diameter, height: component.diameter) let shapeLayer = self.layer as! CAShapeLayer if self.currentComponent?.backgroundColor != component.backgroundColor { shapeLayer.strokeColor = component.backgroundColor.cgColor shapeLayer.lineWidth = lineWidth } if self.currentComponent?.foregroundColor != component.foregroundColor { self.foregroundShapeLayer.strokeColor = component.foregroundColor.cgColor self.foregroundShapeLayer.lineWidth = lineWidth } if self.currentComponent?.diameter != component.diameter { let path = UIBezierPath(arcCenter: CGPoint(x: component.diameter / 2.0, y: component.diameter / 2.0), radius: component.diameter / 2.0, startAngle: -CGFloat.pi / 2.0, endAngle: 2.0 * CGFloat.pi - CGFloat.pi / 2.0, clockwise: true).cgPath shapeLayer.path = path self.foregroundShapeLayer.path = path self.foregroundShapeLayer.frame = CGRect(origin: CGPoint(), size: size) } if self.currentComponent != nil { let previousValue: CGFloat = self.foregroundShapeLayer.presentation()?.strokeEnd ?? self.foregroundShapeLayer.strokeEnd self.foregroundShapeLayer.animate(from: CGFloat(previousValue) as NSNumber, to: CGFloat(component.value) as NSNumber, keyPath: "strokeEnd", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.12) } self.foregroundShapeLayer.strokeEnd = CGFloat(component.value) self.currentComponent = component return size } } public func makeView() -> View { return View() } public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, transition: transition) } }