mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
217 lines
8.0 KiB
Swift
217 lines
8.0 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
|
|
private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: CGFloat = 22.0, lineWidth: CGFloat = 2.0) -> UIImage? {
|
|
return generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setStrokeColor(color.cgColor)
|
|
context.setLineWidth(lineWidth)
|
|
context.setLineCap(.round)
|
|
let cutoutAngle: CGFloat = CGFloat.pi * 30.0 / 180.0
|
|
context.addArc(center: CGPoint(x: size.width / 2.0, y: size.height / 2.0), radius: size.width / 2.0 - lineWidth / 2.0, startAngle: 0.0, endAngle: CGFloat.pi * 2.0 - cutoutAngle, clockwise: false)
|
|
context.strokePath()
|
|
})
|
|
}
|
|
|
|
private func convertIndicatorColor(_ color: UIColor) -> UIColor {
|
|
if color.isEqual(UIColor(rgb: 0x007ee5)) {
|
|
return .gray
|
|
} else if color.isEqual(UIColor(rgb: 0x2ea6ff)) {
|
|
return .white
|
|
} else if color.isEqual(UIColor(rgb: 0x000000)) || color.isEqual(UIColor.black) {
|
|
return .gray
|
|
} else {
|
|
return color
|
|
}
|
|
}
|
|
|
|
public enum ActivityIndicatorType: Equatable {
|
|
case navigationAccent(UIColor)
|
|
case custom(UIColor, CGFloat, CGFloat, Bool)
|
|
|
|
public static func ==(lhs: ActivityIndicatorType, rhs: ActivityIndicatorType) -> Bool {
|
|
switch lhs {
|
|
case let .navigationAccent(lhsColor):
|
|
if case let .navigationAccent(rhsColor) = rhs, lhsColor.isEqual(rhsColor) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .custom(lhsColor, lhsDiameter, lhsWidth, lhsForceCustom):
|
|
if case let .custom(rhsColor, rhsDiameter, rhsWidth, rhsForceCustom) = rhs, lhsColor.isEqual(rhsColor), lhsDiameter == rhsDiameter, lhsWidth == rhsWidth, lhsForceCustom == rhsForceCustom {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ActivityIndicatorSpeed {
|
|
case regular
|
|
case slow
|
|
}
|
|
|
|
public final class ActivityIndicator: ASDisplayNode {
|
|
public var type: ActivityIndicatorType {
|
|
didSet {
|
|
switch self.type {
|
|
case let .navigationAccent(color):
|
|
self.indicatorNode.image = generateIndefiniteActivityIndicatorImage(color: color)
|
|
case let .custom(color, diameter, lineWidth, _):
|
|
self.indicatorNode.image = generateIndefiniteActivityIndicatorImage(color: color, diameter: diameter, lineWidth: lineWidth)
|
|
}
|
|
|
|
switch self.type {
|
|
case let .navigationAccent(color):
|
|
self.indicatorView?.color = color
|
|
case let .custom(color, _, _, _):
|
|
self.indicatorView?.color = convertIndicatorColor(color)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var currentInHierarchy = false
|
|
|
|
override public var isHidden: Bool {
|
|
didSet {
|
|
self.updateAnimation()
|
|
}
|
|
}
|
|
|
|
private let speed: ActivityIndicatorSpeed
|
|
|
|
private let indicatorNode: ASImageNode
|
|
private var indicatorView: UIActivityIndicatorView?
|
|
|
|
public init(type: ActivityIndicatorType, speed: ActivityIndicatorSpeed = .regular) {
|
|
self.type = type
|
|
self.speed = speed
|
|
|
|
self.indicatorNode = ASImageNode()
|
|
self.indicatorNode.isLayerBacked = true
|
|
self.indicatorNode.displayWithoutProcessing = true
|
|
self.indicatorNode.displaysAsynchronously = false
|
|
|
|
super.init()
|
|
|
|
if case let .custom(_, _, _, forceCustom) = self.type, forceCustom {
|
|
self.isLayerBacked = true
|
|
}
|
|
|
|
switch type {
|
|
case let .navigationAccent(color):
|
|
self.indicatorNode.image = generateIndefiniteActivityIndicatorImage(color: color)
|
|
case let .custom(color, diameter, lineWidth, forceCustom):
|
|
self.indicatorNode.image = generateIndefiniteActivityIndicatorImage(color: color, diameter: diameter, lineWidth: lineWidth)
|
|
if forceCustom {
|
|
self.addSubnode(self.indicatorNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
override public func didLoad() {
|
|
super.didLoad()
|
|
|
|
let indicatorView = UIActivityIndicatorView(style: .whiteLarge)
|
|
switch self.type {
|
|
case let .navigationAccent(color):
|
|
indicatorView.color = color
|
|
case let .custom(color, _, _, forceCustom):
|
|
indicatorView.color = convertIndicatorColor(color)
|
|
if !forceCustom {
|
|
self.view.addSubview(indicatorView)
|
|
}
|
|
}
|
|
self.indicatorView = indicatorView
|
|
let size = self.bounds.size
|
|
if !size.width.isZero {
|
|
self.layoutContents(size: size)
|
|
}
|
|
}
|
|
|
|
private var isAnimating = false {
|
|
didSet {
|
|
if self.isAnimating != oldValue {
|
|
if self.isAnimating {
|
|
self.indicatorView?.startAnimating()
|
|
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
|
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
|
switch self.speed {
|
|
case .regular:
|
|
basicAnimation.duration = 0.5
|
|
case .slow:
|
|
basicAnimation.duration = 0.7
|
|
}
|
|
basicAnimation.fromValue = NSNumber(value: Float(0.0))
|
|
basicAnimation.toValue = NSNumber(value: Float.pi * 2.0)
|
|
basicAnimation.repeatCount = Float.infinity
|
|
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
|
basicAnimation.beginTime = 1.0
|
|
|
|
self.indicatorNode.layer.add(basicAnimation, forKey: "progressRotation")
|
|
} else {
|
|
self.indicatorView?.stopAnimating()
|
|
self.indicatorNode.layer.removeAnimation(forKey: "progressRotation")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updateAnimation() {
|
|
self.isAnimating = !self.isHidden && self.currentInHierarchy
|
|
}
|
|
|
|
override public func willEnterHierarchy() {
|
|
super.willEnterHierarchy()
|
|
|
|
self.currentInHierarchy = true
|
|
self.updateAnimation()
|
|
}
|
|
|
|
override public func didExitHierarchy() {
|
|
super.didExitHierarchy()
|
|
|
|
self.currentInHierarchy = false
|
|
self.updateAnimation()
|
|
}
|
|
|
|
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
|
switch self.type {
|
|
case .navigationAccent:
|
|
return CGSize(width: 22.0, height: 22.0)
|
|
case let .custom(_, diameter, _, _):
|
|
return CGSize(width: diameter, height: diameter)
|
|
}
|
|
}
|
|
|
|
override public func layout() {
|
|
super.layout()
|
|
|
|
let size = self.bounds.size
|
|
|
|
self.layoutContents(size: size)
|
|
}
|
|
|
|
private func layoutContents(size: CGSize) {
|
|
let indicatorSize: CGSize
|
|
let shouldScale: Bool
|
|
switch self.type {
|
|
case .navigationAccent:
|
|
indicatorSize = CGSize(width: 22.0, height: 22.0)
|
|
shouldScale = false
|
|
case let .custom(_, diameter, _, forceDefault):
|
|
indicatorSize = CGSize(width: diameter, height: diameter)
|
|
shouldScale = !forceDefault
|
|
}
|
|
self.indicatorNode.frame = CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize)
|
|
if shouldScale, let indicatorView = self.indicatorView {
|
|
let intrinsicSize = indicatorView.bounds.size
|
|
self.subnodeTransform = CATransform3DMakeScale(min(1.0, indicatorSize.width / intrinsicSize.width), min(1.0, indicatorSize.height / intrinsicSize.height), 1.0)
|
|
indicatorView.center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
|
}
|
|
}
|
|
}
|