Swiftgram/TelegramUI/RadialStatusNode.swift
Peter fc8fa045a6 Fixed Apple Pay
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
2018-10-13 03:31:39 +03:00

250 lines
11 KiB
Swift

import Foundation
import AsyncDisplayKit
public enum RadialStatusNodeState: Equatable {
case none
case download(UIColor)
case play(UIColor)
case pause(UIColor)
case progress(color: UIColor, lineWidth: CGFloat?, value: CGFloat?, cancelEnabled: Bool)
case cloudProgress(color: UIColor, strokeBackgroundColor: UIColor, lineWidth: CGFloat, value: CGFloat?)
case check(UIColor)
case customIcon(UIImage)
case secretTimeout(color: UIColor, icon: UIImage?, beginTime: Double, timeout: Double)
public static func ==(lhs: RadialStatusNodeState, rhs: RadialStatusNodeState) -> Bool {
switch lhs {
case .none:
if case .none = rhs {
return true
} else {
return false
}
case let .download(lhsColor):
if case let .download(rhsColor) = rhs, lhsColor.isEqual(rhsColor) {
return true
} else {
return false
}
case let .play(lhsColor):
if case let .play(rhsColor) = rhs, lhsColor.isEqual(rhsColor) {
return true
} else {
return false
}
case let .pause(lhsColor):
if case let .pause(rhsColor) = rhs, lhsColor.isEqual(rhsColor) {
return true
} else {
return false
}
case let .progress(lhsColor, lhsLineWidth, lhsValue, lhsCancelEnabled):
if case let .progress(rhsColor, rhsLineWidth, rhsValue, rhsCancelEnabled) = rhs, lhsColor.isEqual(rhsColor), lhsValue == rhsValue, lhsLineWidth == rhsLineWidth, lhsCancelEnabled == rhsCancelEnabled {
return true
} else {
return false
}
case let .cloudProgress(lhsColor, lhsStrokeBackgroundColor, lhsLineWidth, lhsValue):
if case let .cloudProgress(rhsColor, rhsStrokeBackgroundColor, rhsLineWidth, rhsValue) = rhs, lhsColor.isEqual(rhsColor), lhsStrokeBackgroundColor.isEqual(rhsStrokeBackgroundColor), lhsLineWidth.isEqual(to: rhsLineWidth), lhsValue == rhsValue {
return true
} else {
return false
}
case let .check(lhsColor):
if case let .check(rhsColor) = rhs, lhsColor.isEqual(rhsColor) {
return true
} else {
return false
}
case let .customIcon(lhsImage):
if case let .customIcon(rhsImage) = rhs, lhsImage === rhsImage {
return true
} else {
return false
}
case let .secretTimeout(lhsColor, lhsIcon, lhsBeginTime, lhsTimeout):
if case let .secretTimeout(rhsColor, rhsIcon, rhsBeginTime, rhsTimeout) = rhs, lhsColor.isEqual(rhsColor), lhsIcon === rhsIcon, lhsBeginTime.isEqual(to: rhsBeginTime), lhsTimeout.isEqual(to: rhsTimeout) {
return true
} else {
return false
}
}
}
func backgroundColor(color: UIColor) -> UIColor? {
switch self {
case .none:
return nil
default:
return color
}
}
func contentNode(current: RadialStatusContentNode?) -> RadialStatusContentNode? {
switch self {
case .none:
return nil
case let .download(color):
return RadialStatusIconContentNode(icon: .download(color))
case let .play(color):
return RadialStatusIconContentNode(icon: .play(color))
case let .pause(color):
return RadialStatusIconContentNode(icon: .pause(color))
case let .customIcon(image):
return RadialStatusIconContentNode(icon: .custom(image))
case let .check(color):
return RadialCheckContentNode(color: color)
case let .progress(color, lineWidth, value, cancelEnabled):
if let current = current as? RadialProgressContentNode, current.displayCancel == cancelEnabled {
if !current.color.isEqual(color) {
current.color = color
}
current.progress = value
return current
} else {
let node = RadialProgressContentNode(color: color, lineWidth: lineWidth, displayCancel: cancelEnabled)
node.progress = value
return node
}
case let .cloudProgress(color, strokeLineColor, lineWidth, value):
if let current = current as? RadialCloudProgressContentNode {
if !current.color.isEqual(color) {
current.color = color
}
current.progress = value
return current
} else {
let node = RadialCloudProgressContentNode(color: color, backgroundStrokeColor: strokeLineColor, lineWidth: lineWidth)
node.progress = value
return node
}
case let .secretTimeout(color, icon, beginTime, timeout):
return RadialStatusSecretTimeoutContentNode(color: color, beginTime: beginTime, timeout: timeout, icon: icon)
}
}
}
public final class RadialStatusNode: ASControlNode {
private var backgroundNodeColor: UIColor
private(set) var state: RadialStatusNodeState = .none
private var backgroundNode: RadialStatusBackgroundNode?
private var contentNode: RadialStatusContentNode?
private var nextContentNode: RadialStatusContentNode?
public init(backgroundNodeColor: UIColor) {
self.backgroundNodeColor = backgroundNodeColor
super.init()
}
public func transitionToState(_ state: RadialStatusNodeState, animated: Bool = true, completion: @escaping () -> Void) {
if self.state != state {
self.state = state
let contentNode = state.contentNode(current: self.contentNode)
if contentNode !== self.contentNode {
self.transitionToContentNode(contentNode, backgroundColor: state.backgroundColor(color: self.backgroundNodeColor), animated: animated, completion: completion)
} else {
self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), animated: animated, completion: completion)
}
} else {
completion()
}
}
private func transitionToContentNode(_ node: RadialStatusContentNode?, backgroundColor: UIColor?, animated: Bool, completion: @escaping () -> Void) {
if let contentNode = self.contentNode {
self.nextContentNode = node
contentNode.enqueueReadyForTransition { [weak contentNode, weak self] in
if let strongSelf = self, let contentNode = contentNode, strongSelf.contentNode === contentNode {
if animated {
strongSelf.contentNode = strongSelf.nextContentNode
contentNode.animateOut { [weak contentNode] in
if let strongSelf = self, let contentNode = contentNode {
if contentNode !== strongSelf.contentNode {
contentNode.removeFromSupernode()
}
}
}
if let contentNode = strongSelf.contentNode {
strongSelf.addSubnode(contentNode)
contentNode.frame = strongSelf.bounds
if strongSelf.isNodeLoaded {
contentNode.layout()
contentNode.animateIn()
}
}
strongSelf.transitionToBackgroundColor(backgroundColor, animated: animated, completion: completion)
} else {
contentNode.removeFromSupernode()
strongSelf.contentNode = strongSelf.nextContentNode
if let contentNode = strongSelf.contentNode {
strongSelf.addSubnode(contentNode)
contentNode.frame = strongSelf.bounds
if strongSelf.isNodeLoaded {
contentNode.layout()
}
}
strongSelf.transitionToBackgroundColor(backgroundColor, animated: animated, completion: completion)
}
}
}
} else {
self.contentNode = node
if let contentNode = self.contentNode {
contentNode.frame = self.bounds
self.addSubnode(contentNode)
}
self.transitionToBackgroundColor(backgroundColor, animated: animated, completion: completion)
}
}
private func transitionToBackgroundColor(_ color: UIColor?, animated: Bool, completion: @escaping () -> Void) {
let currentColor = self.backgroundNode?.color
var updated = false
if let color = color, let currentColor = currentColor {
updated = !color.isEqual(currentColor)
} else if (currentColor != nil) != (color != nil) {
updated = true
}
if updated {
if let color = color {
if let backgroundNode = self.backgroundNode {
backgroundNode.color = color
completion()
} else {
let backgroundNode = RadialStatusBackgroundNode(color: color)
backgroundNode.frame = self.bounds
self.backgroundNode = backgroundNode
self.insertSubnode(backgroundNode, at: 0)
completion()
}
} else if let backgroundNode = self.backgroundNode {
self.backgroundNode = nil
if animated {
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak backgroundNode] _ in
backgroundNode?.removeFromSupernode()
completion()
})
} else {
backgroundNode.removeFromSupernode()
completion()
}
}
} else {
completion()
}
}
override public func layout() {
self.backgroundNode?.frame = self.bounds
if let contentNode = self.contentNode {
contentNode.frame = self.bounds
}
}
}