mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00

Added ability to download music without streaming Added progress indicators for various blocking tasks Fixed image gallery swipe to dismiss after zooming Added online member count indication in supergroups Fixed contact statuses in contact search
302 lines
11 KiB
Swift
302 lines
11 KiB
Swift
import Foundation
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import LegacyComponents
|
|
|
|
private final class RadialCloudProgressContentCancelNodeParameters: NSObject {
|
|
let color: UIColor
|
|
|
|
init(color: UIColor) {
|
|
self.color = color
|
|
}
|
|
}
|
|
|
|
private final class RadialCloudProgressContentSpinnerNodeParameters: NSObject {
|
|
let color: UIColor
|
|
let backgroundStrokeColor: UIColor
|
|
let progress: CGFloat
|
|
let lineWidth: CGFloat?
|
|
|
|
init(color: UIColor, backgroundStrokeColor: UIColor, progress: CGFloat, lineWidth: CGFloat?) {
|
|
self.color = color
|
|
self.backgroundStrokeColor = backgroundStrokeColor
|
|
self.progress = progress
|
|
self.lineWidth = lineWidth
|
|
}
|
|
}
|
|
|
|
private final class RadialCloudProgressContentSpinnerNode: ASDisplayNode {
|
|
var progressAnimationCompleted: (() -> Void)?
|
|
|
|
var color: UIColor {
|
|
didSet {
|
|
self.setNeedsDisplay()
|
|
}
|
|
}
|
|
|
|
var backgroundStrokeColor: UIColor {
|
|
didSet {
|
|
self.setNeedsDisplay()
|
|
}
|
|
}
|
|
|
|
private var effectiveProgress: CGFloat = 0.0 {
|
|
didSet {
|
|
self.setNeedsDisplay()
|
|
}
|
|
}
|
|
|
|
var progress: CGFloat? {
|
|
didSet {
|
|
self.pop_removeAnimation(forKey: "progress")
|
|
if let progress = self.progress {
|
|
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
|
|
|
let animation = POPBasicAnimation()
|
|
animation.property = POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
|
property?.readBlock = { node, values in
|
|
values?.pointee = (node as! RadialCloudProgressContentSpinnerNode).effectiveProgress
|
|
}
|
|
property?.writeBlock = { node, values in
|
|
(node as! RadialCloudProgressContentSpinnerNode).effectiveProgress = values!.pointee
|
|
}
|
|
property?.threshold = 0.01
|
|
}) as! POPAnimatableProperty
|
|
animation.fromValue = CGFloat(self.effectiveProgress) as NSNumber
|
|
animation.toValue = CGFloat(progress) as NSNumber
|
|
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
|
animation.duration = 0.2
|
|
animation.completionBlock = { [weak self] _, _ in
|
|
self?.progressAnimationCompleted?()
|
|
}
|
|
self.pop_add(animation, forKey: "progress")
|
|
} else if self.pop_animation(forKey: "indefiniteProgress") == nil {
|
|
let animation = POPBasicAnimation()
|
|
animation.property = POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
|
property?.readBlock = { node, values in
|
|
values?.pointee = (node as! RadialCloudProgressContentSpinnerNode).effectiveProgress
|
|
}
|
|
property?.writeBlock = { node, values in
|
|
(node as! RadialCloudProgressContentSpinnerNode).effectiveProgress = values!.pointee
|
|
}
|
|
property?.threshold = 0.01
|
|
}) as! POPAnimatableProperty
|
|
animation.fromValue = CGFloat(0.0) as NSNumber
|
|
animation.toValue = CGFloat(2.0) as NSNumber
|
|
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
|
animation.duration = 2.5
|
|
animation.repeatForever = true
|
|
self.pop_add(animation, forKey: "indefiniteProgress")
|
|
}
|
|
}
|
|
}
|
|
|
|
var isAnimatingProgress: Bool {
|
|
return self.pop_animation(forKey: "progress") != nil
|
|
}
|
|
|
|
let lineWidth: CGFloat?
|
|
|
|
init(color: UIColor, backgroundStrokeColor: UIColor, lineWidth: CGFloat?) {
|
|
self.color = color
|
|
self.backgroundStrokeColor = backgroundStrokeColor
|
|
self.lineWidth = lineWidth
|
|
|
|
super.init()
|
|
|
|
self.isLayerBacked = true
|
|
self.displaysAsynchronously = true
|
|
self.isOpaque = false
|
|
}
|
|
|
|
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
|
return RadialCloudProgressContentSpinnerNodeParameters(color: self.color, backgroundStrokeColor: self.backgroundStrokeColor, progress: self.effectiveProgress, lineWidth: self.lineWidth)
|
|
}
|
|
|
|
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
|
let context = UIGraphicsGetCurrentContext()!
|
|
|
|
if !isRasterizing {
|
|
context.setBlendMode(.copy)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.fill(bounds)
|
|
}
|
|
|
|
if let parameters = parameters as? RadialCloudProgressContentSpinnerNodeParameters {
|
|
let factor = bounds.size.width / 50.0
|
|
|
|
var progress = parameters.progress
|
|
var startAngle = -CGFloat.pi / 2.0
|
|
var endAngle = CGFloat(progress) * 2.0 * CGFloat.pi + startAngle
|
|
|
|
if progress > 1.0 {
|
|
progress = 2.0 - progress
|
|
let tmp = startAngle
|
|
startAngle = endAngle
|
|
endAngle = tmp
|
|
}
|
|
progress = min(1.0, progress)
|
|
|
|
let lineWidth: CGFloat = parameters.lineWidth ?? max(1.6, 2.25 * factor)
|
|
|
|
let pathDiameter: CGFloat
|
|
if parameters.lineWidth != nil {
|
|
pathDiameter = bounds.size.width - lineWidth
|
|
} else {
|
|
pathDiameter = bounds.size.width - lineWidth - 2.5 * 2.0
|
|
}
|
|
|
|
context.setStrokeColor(parameters.backgroundStrokeColor.cgColor)
|
|
let backgroundPath = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: 0.0, endAngle: 2.0 * CGFloat.pi, clockwise:true)
|
|
backgroundPath.lineWidth = lineWidth
|
|
backgroundPath.stroke()
|
|
|
|
context.setStrokeColor(parameters.color.cgColor)
|
|
let path = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise:true)
|
|
path.lineWidth = lineWidth
|
|
path.lineCapStyle = .round
|
|
path.stroke()
|
|
}
|
|
}
|
|
|
|
override func willEnterHierarchy() {
|
|
super.willEnterHierarchy()
|
|
|
|
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
|
basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
|
basicAnimation.duration = 2.0
|
|
basicAnimation.fromValue = NSNumber(value: Float(0.0))
|
|
basicAnimation.toValue = NSNumber(value: Float.pi * 2.0)
|
|
basicAnimation.repeatCount = Float.infinity
|
|
basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
|
basicAnimation.beginTime = 1.0
|
|
|
|
self.layer.add(basicAnimation, forKey: "progressRotation")
|
|
}
|
|
|
|
override func didExitHierarchy() {
|
|
super.didExitHierarchy()
|
|
|
|
self.layer.removeAnimation(forKey: "progressRotation")
|
|
}
|
|
}
|
|
|
|
private final class RadialCloudProgressContentCancelNode: ASDisplayNode {
|
|
var color: UIColor {
|
|
didSet {
|
|
self.setNeedsDisplay()
|
|
}
|
|
}
|
|
|
|
init(color: UIColor) {
|
|
self.color = color
|
|
|
|
super.init()
|
|
|
|
self.isLayerBacked = true
|
|
self.displaysAsynchronously = true
|
|
self.isOpaque = false
|
|
}
|
|
|
|
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
|
return RadialCloudProgressContentCancelNodeParameters(color: self.color)
|
|
}
|
|
|
|
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
|
let context = UIGraphicsGetCurrentContext()!
|
|
|
|
if !isRasterizing {
|
|
context.setBlendMode(.copy)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.fill(bounds)
|
|
}
|
|
|
|
if let parameters = parameters as? RadialCloudProgressContentCancelNodeParameters {
|
|
let size: CGFloat = 8.0
|
|
context.setFillColor(parameters.color.cgColor)
|
|
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: floor((bounds.size.width - size) / 2.0), y: floor((bounds.size.height - size) / 2.0)), size: CGSize(width: size, height: size)), cornerRadius: 1.0)
|
|
path.fill()
|
|
}
|
|
}
|
|
}
|
|
|
|
final class RadialCloudProgressContentNode: RadialStatusContentNode {
|
|
private let spinnerNode: RadialCloudProgressContentSpinnerNode
|
|
private let cancelNode: RadialCloudProgressContentCancelNode
|
|
|
|
var color: UIColor {
|
|
didSet {
|
|
self.setNeedsDisplay()
|
|
self.spinnerNode.color = self.color
|
|
}
|
|
}
|
|
|
|
var backgroundStrokeColor: UIColor {
|
|
didSet {
|
|
self.setNeedsDisplay()
|
|
self.spinnerNode.backgroundStrokeColor = self.backgroundStrokeColor
|
|
}
|
|
}
|
|
|
|
var progress: CGFloat? = 0.0 {
|
|
didSet {
|
|
self.spinnerNode.progress = self.progress
|
|
}
|
|
}
|
|
|
|
private var enqueuedReadyForTransition: (() -> Void)?
|
|
|
|
init(color: UIColor, backgroundStrokeColor: UIColor, lineWidth: CGFloat?) {
|
|
self.color = color
|
|
self.backgroundStrokeColor = backgroundStrokeColor
|
|
|
|
self.spinnerNode = RadialCloudProgressContentSpinnerNode(color: color, backgroundStrokeColor: backgroundStrokeColor, lineWidth: lineWidth)
|
|
self.cancelNode = RadialCloudProgressContentCancelNode(color: color)
|
|
|
|
super.init()
|
|
|
|
self.isLayerBacked = true
|
|
|
|
self.addSubnode(self.spinnerNode)
|
|
self.addSubnode(self.cancelNode)
|
|
|
|
self.spinnerNode.progressAnimationCompleted = { [weak self] in
|
|
if let strongSelf = self {
|
|
if let enqueuedReadyForTransition = strongSelf.enqueuedReadyForTransition {
|
|
strongSelf.enqueuedReadyForTransition = nil
|
|
enqueuedReadyForTransition()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override func enqueueReadyForTransition(_ f: @escaping () -> Void) {
|
|
if self.spinnerNode.isAnimatingProgress {
|
|
self.enqueuedReadyForTransition = f
|
|
} else {
|
|
f()
|
|
}
|
|
}
|
|
|
|
override func layout() {
|
|
super.layout()
|
|
|
|
let bounds = self.bounds
|
|
self.spinnerNode.bounds = bounds
|
|
self.spinnerNode.position = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
|
self.cancelNode.frame = bounds
|
|
}
|
|
|
|
override func animateOut(completion: @escaping () -> Void) {
|
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { _ in
|
|
completion()
|
|
})
|
|
self.cancelNode.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
|
|
}
|
|
|
|
override func animateIn() {
|
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
|
self.cancelNode.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15)
|
|
}
|
|
}
|