2024-11-07 16:32:33 +01:00

172 lines
6.7 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import ManagedAnimationNode
import ContextUI
public final class MoreButtonNode: ASDisplayNode {
public class MoreIconNode: ManagedAnimationNode {
public enum State: Equatable {
case more
case search
}
private let encircled: Bool
private let duration: Double = 0.21
public var iconState: State = .search
init(size: CGSize = CGSize(width: 30.0, height: 30.0), encircled: Bool) {
self.encircled = encircled
super.init(size: size)
if self.encircled {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moretosearch"), frames: .range(startFrame: 90, endFrame: 90), duration: 0.0))
} else {
self.iconState = .more
self.trackTo(item: ManagedAnimationItem(source: .local("anim_baremoredots"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.0))
}
}
func play() {
if case .more = self.iconState {
let animationName = self.encircled ? "anim_moredots" : "anim_baremoredots"
self.trackTo(item: ManagedAnimationItem(source: .local(animationName), frames: .range(startFrame: 0, endFrame: 46), duration: 0.76))
}
}
public func enqueueState(_ state: State, animated: Bool) {
guard self.iconState != state else {
return
}
let previousState = self.iconState
self.iconState = state
let source = ManagedAnimationSource.local("anim_moretosearch")
let totalLength: Int = 90
if animated {
switch previousState {
case .more:
switch state {
case .more:
break
case .search:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: totalLength), duration: self.duration))
}
case .search:
switch state {
case .more:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: 0), duration: self.duration))
case .search:
break
}
}
} else {
switch state {
case .more:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: 0), duration: 0.0))
case .search:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: totalLength), duration: 0.0))
}
}
}
}
public var action: ((ASDisplayNode, ContextGesture?) -> Void)?
private let containerNode: ContextControllerSourceNode
public let contextSourceNode: ContextReferenceContentNode
private let buttonNode: HighlightableButtonNode
public let iconNode: MoreIconNode
private var color: UIColor?
public var theme: PresentationTheme {
didSet {
self.update()
}
}
private let size: CGSize
public func updateColor(_ color: UIColor?, transition: ContainedViewLayoutTransition) {
self.color = color
if case let .animated(duration, curve) = transition {
if let snapshotView = self.iconNode.view.snapshotContentTree() {
snapshotView.frame = self.iconNode.frame
self.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: false, completion: { _ in
snapshotView.removeFromSuperview()
})
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: curve.timingFunction)
}
}
self.update()
}
private func update() {
let color = self.color ?? self.theme.rootController.navigationBar.buttonColor
self.iconNode.customColor = color
}
public init(theme: PresentationTheme, size: CGSize = CGSize(width: 30.0, height: 30.0), encircled: Bool = true) {
self.theme = theme
self.size = size
self.contextSourceNode = ContextReferenceContentNode()
self.containerNode = ContextControllerSourceNode()
self.containerNode.animateScale = false
self.buttonNode = HighlightableButtonNode()
self.iconNode = MoreIconNode(size: size, encircled: encircled)
self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor
super.init()
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.contextSourceNode)
self.contextSourceNode.addSubnode(self.iconNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else {
return
}
if case .more = strongSelf.iconNode.iconState {
strongSelf.action?(strongSelf.contextSourceNode, gesture)
}
}
}
@objc public func buttonPressed() {
self.action?(self.contextSourceNode, nil)
if case .more = self.iconNode.iconState {
self.iconNode.play()
}
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
let animationSize = self.size
let inset: CGFloat = 0.0
let iconFrame = CGRect(origin: CGPoint(x: inset + 6.0, y: floor((constrainedSize.height - animationSize.height) / 2.0) + 1.0), size: animationSize)
self.iconNode.position = iconFrame.center
self.iconNode.bounds = CGRect(origin: .zero, size: iconFrame.size)
let size = CGSize(width: animationSize.width + inset * 2.0, height: constrainedSize.height)
let bounds = CGRect(origin: CGPoint(), size: size)
self.buttonNode.frame = bounds
self.containerNode.frame = bounds
self.contextSourceNode.frame = bounds
return size
}
}