mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
287 lines
14 KiB
Swift
287 lines
14 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import ContextUI
|
|
import TelegramPresentationData
|
|
import ManagedAnimationNode
|
|
import Display
|
|
|
|
private enum MoreIconNodeState: Equatable {
|
|
case more
|
|
case search
|
|
case moreToSearch(Float)
|
|
}
|
|
|
|
private final class MoreIconNode: ManagedAnimationNode {
|
|
private let duration: Double = 0.21
|
|
private var iconState: MoreIconNodeState = .more
|
|
|
|
init() {
|
|
super.init(size: CGSize(width: 30.0, height: 30.0))
|
|
|
|
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moretosearch"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.0))
|
|
}
|
|
|
|
func play() {
|
|
if case .more = self.iconState {
|
|
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moredots"), frames: .range(startFrame: 0, endFrame: 46), duration: 0.76))
|
|
}
|
|
}
|
|
|
|
func enqueueState(_ state: MoreIconNodeState, 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 let .moreToSearch(progress):
|
|
let frame = Int(progress * Float(totalLength))
|
|
let duration = self.duration * Double(progress)
|
|
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: frame), duration: 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
|
|
case let .moreToSearch(progress):
|
|
let frame = Int(progress * Float(totalLength))
|
|
let duration = self.duration * Double((1.0 - progress))
|
|
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: frame), duration: duration))
|
|
}
|
|
case let .moreToSearch(currentProgress):
|
|
let currentFrame = Int(currentProgress * Float(totalLength))
|
|
switch state {
|
|
case .more:
|
|
let duration = self.duration * Double(currentProgress)
|
|
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: currentFrame, endFrame: 0), duration: duration))
|
|
case .search:
|
|
let duration = self.duration * (1.0 - Double(currentProgress))
|
|
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: currentFrame, endFrame: totalLength), duration: duration))
|
|
case let .moreToSearch(progress):
|
|
let frame = Int(progress * Float(totalLength))
|
|
let duration = self.duration * Double(abs(currentProgress - progress))
|
|
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: currentFrame, endFrame: frame), duration: duration))
|
|
}
|
|
}
|
|
} 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))
|
|
case let .moreToSearch(progress):
|
|
let frame = Int(progress * Float(totalLength))
|
|
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: frame, endFrame: frame), duration: 0.0))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
|
|
let containerNode: ContextControllerSourceNode
|
|
let contextSourceNode: ContextReferenceContentNode
|
|
private let regularTextNode: ImmediateTextNode
|
|
private let whiteTextNode: ImmediateTextNode
|
|
private let iconNode: ASImageNode
|
|
private var animationNode: MoreIconNode?
|
|
|
|
private var key: PeerInfoHeaderNavigationButtonKey?
|
|
private var theme: PresentationTheme?
|
|
|
|
var isWhite: Bool = false {
|
|
didSet {
|
|
if self.isWhite != oldValue {
|
|
if case .qrCode = self.key, let theme = self.theme {
|
|
self.iconNode.image = self.isWhite ? generateTintedImage(image: PresentationResourcesRootController.navigationQrCodeIcon(theme), color: .white) : PresentationResourcesRootController.navigationQrCodeIcon(theme)
|
|
} else if case .postStory = self.key, let theme = self.theme {
|
|
self.iconNode.image = self.isWhite ? generateTintedImage(image: PresentationResourcesRootController.navigationPostStoryIcon(theme), color: .white) : PresentationResourcesRootController.navigationPostStoryIcon(theme)
|
|
}
|
|
|
|
self.regularTextNode.isHidden = self.isWhite
|
|
self.whiteTextNode.isHidden = !self.isWhite
|
|
self.animationNode?.view.tintColor = self.isWhite ? .white : self.theme?.list.itemAccentColor
|
|
self.animationNode?.imageNode.layer.layerTintColor = self.isWhite ? UIColor.white.cgColor : self.theme?.list.itemAccentColor.cgColor
|
|
}
|
|
}
|
|
}
|
|
|
|
var action: ((ASDisplayNode, ContextGesture?) -> Void)?
|
|
|
|
init() {
|
|
self.contextSourceNode = ContextReferenceContentNode()
|
|
self.containerNode = ContextControllerSourceNode()
|
|
self.containerNode.animateScale = false
|
|
|
|
self.regularTextNode = ImmediateTextNode()
|
|
self.whiteTextNode = ImmediateTextNode()
|
|
self.whiteTextNode.isHidden = true
|
|
|
|
self.iconNode = ASImageNode()
|
|
self.iconNode.displaysAsynchronously = false
|
|
self.iconNode.displayWithoutProcessing = true
|
|
|
|
super.init(pointerStyle: .insetRectangle(-8.0, 2.0))
|
|
|
|
self.isAccessibilityElement = true
|
|
self.accessibilityTraits = .button
|
|
|
|
self.containerNode.addSubnode(self.contextSourceNode)
|
|
self.contextSourceNode.addSubnode(self.regularTextNode)
|
|
self.contextSourceNode.addSubnode(self.whiteTextNode)
|
|
self.contextSourceNode.addSubnode(self.iconNode)
|
|
|
|
self.addSubnode(self.containerNode)
|
|
|
|
self.containerNode.activated = { [weak self] gesture, _ in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.action?(strongSelf.contextSourceNode, gesture)
|
|
}
|
|
|
|
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
|
}
|
|
|
|
@objc private func pressed() {
|
|
self.animationNode?.play()
|
|
self.action?(self.contextSourceNode, nil)
|
|
}
|
|
|
|
func update(key: PeerInfoHeaderNavigationButtonKey, presentationData: PresentationData, height: CGFloat) -> CGSize {
|
|
let textSize: CGSize
|
|
let isFirstTime = self.key == nil
|
|
if self.key != key || self.theme !== presentationData.theme {
|
|
self.key = key
|
|
self.theme = presentationData.theme
|
|
|
|
let text: String
|
|
var accessibilityText: String
|
|
var icon: UIImage?
|
|
var isBold = false
|
|
var isGestureEnabled = false
|
|
var isAnimation = false
|
|
var animationState: MoreIconNodeState = .more
|
|
switch key {
|
|
case .edit:
|
|
text = presentationData.strings.Common_Edit
|
|
accessibilityText = text
|
|
case .done, .cancel, .selectionDone:
|
|
text = presentationData.strings.Common_Done
|
|
accessibilityText = text
|
|
isBold = true
|
|
case .select:
|
|
text = presentationData.strings.Common_Select
|
|
accessibilityText = text
|
|
case .search:
|
|
text = ""
|
|
accessibilityText = presentationData.strings.Common_Search
|
|
icon = nil// PresentationResourcesRootController.navigationCompactSearchIcon(presentationData.theme)
|
|
isAnimation = true
|
|
animationState = .search
|
|
case .editPhoto:
|
|
text = presentationData.strings.Settings_EditPhoto
|
|
accessibilityText = text
|
|
case .editVideo:
|
|
text = presentationData.strings.Settings_EditVideo
|
|
accessibilityText = text
|
|
case .more:
|
|
text = ""
|
|
accessibilityText = presentationData.strings.Common_More
|
|
icon = nil// PresentationResourcesRootController.navigationMoreCircledIcon(presentationData.theme)
|
|
isGestureEnabled = true
|
|
isAnimation = true
|
|
animationState = .more
|
|
case .qrCode:
|
|
text = ""
|
|
accessibilityText = presentationData.strings.PeerInfo_QRCode_Title
|
|
icon = PresentationResourcesRootController.navigationQrCodeIcon(presentationData.theme)
|
|
case .moreToSearch:
|
|
text = ""
|
|
accessibilityText = ""
|
|
case .postStory:
|
|
text = ""
|
|
accessibilityText = presentationData.strings.Story_Privacy_PostStory
|
|
icon = PresentationResourcesRootController.navigationPostStoryIcon(presentationData.theme)
|
|
}
|
|
self.accessibilityLabel = accessibilityText
|
|
self.containerNode.isGestureEnabled = isGestureEnabled
|
|
|
|
let font: UIFont = isBold ? Font.semibold(17.0) : Font.regular(17.0)
|
|
|
|
self.regularTextNode.attributedText = NSAttributedString(string: text, font: font, textColor: presentationData.theme.rootController.navigationBar.accentTextColor)
|
|
self.whiteTextNode.attributedText = NSAttributedString(string: text, font: font, textColor: .white)
|
|
self.iconNode.image = icon
|
|
|
|
if isAnimation {
|
|
self.iconNode.isHidden = true
|
|
let animationNode: MoreIconNode
|
|
if let current = self.animationNode {
|
|
animationNode = current
|
|
} else {
|
|
animationNode = MoreIconNode()
|
|
self.animationNode = animationNode
|
|
self.contextSourceNode.addSubnode(animationNode)
|
|
}
|
|
animationNode.customColor = .white
|
|
animationNode.imageNode.layer.layerTintColor = self.isWhite ? UIColor.white.cgColor : presentationData.theme.rootController.navigationBar.accentTextColor.cgColor
|
|
animationNode.enqueueState(animationState, animated: !isFirstTime)
|
|
} else {
|
|
self.iconNode.isHidden = false
|
|
if let current = self.animationNode {
|
|
self.animationNode = nil
|
|
current.removeFromSupernode()
|
|
}
|
|
}
|
|
|
|
textSize = self.regularTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
|
let _ = self.whiteTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
|
} else {
|
|
textSize = self.regularTextNode.bounds.size
|
|
}
|
|
|
|
let inset: CGFloat = 0.0
|
|
|
|
let textFrame = CGRect(origin: CGPoint(x: inset, y: floor((height - textSize.height) / 2.0)), size: textSize)
|
|
self.regularTextNode.frame = textFrame
|
|
self.whiteTextNode.frame = textFrame
|
|
|
|
if let animationNode = self.animationNode {
|
|
let animationSize = CGSize(width: 30.0, height: 30.0)
|
|
|
|
animationNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((height - animationSize.height) / 2.0)), size: animationSize)
|
|
|
|
let size = CGSize(width: animationSize.width + inset * 2.0, height: height)
|
|
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
return size
|
|
} else if let image = self.iconNode.image {
|
|
self.iconNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size)
|
|
|
|
let size = CGSize(width: image.size.width + inset * 2.0, height: height)
|
|
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
return size
|
|
} else {
|
|
let size = CGSize(width: textSize.width + inset * 2.0, height: height)
|
|
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
return size
|
|
}
|
|
}
|
|
}
|