mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
320 lines
14 KiB
Swift
320 lines
14 KiB
Swift
import UIKit
|
|
import AppBundle
|
|
import AsyncDisplayKit
|
|
|
|
enum NavigationTransition {
|
|
case Push
|
|
case Pop
|
|
}
|
|
|
|
private let shadowWidth: CGFloat = 16.0
|
|
|
|
private func generateShadow() -> UIImage? {
|
|
return generateImage(CGSize(width: 16.0, height: 1.0), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(UIColor.black.cgColor)
|
|
context.setShadow(offset: CGSize(), blur: 16.0, color: UIColor(white: 0.0, alpha: 0.5).cgColor)
|
|
context.fill(CGRect(origin: CGPoint(x: size.width, y: 0.0), size: CGSize(width: 16.0, height: 1.0)))
|
|
})
|
|
}
|
|
|
|
private let shadowImage = generateShadow()
|
|
|
|
public protocol CustomNavigationTransitionNode: ASDisplayNode {
|
|
func setup(topNavigationBar: NavigationBar, bottomNavigationBar: NavigationBar)
|
|
func update(containerSize: CGSize, fraction: CGFloat, transition: ContainedViewLayoutTransition)
|
|
func restore()
|
|
}
|
|
|
|
final class NavigationTransitionCoordinator {
|
|
private var _progress: CGFloat = 0.0
|
|
var progress: CGFloat {
|
|
get {
|
|
return self._progress
|
|
}
|
|
}
|
|
|
|
private let container: NavigationContainer
|
|
private let transition: NavigationTransition
|
|
let isInteractive: Bool
|
|
let isFlat: Bool
|
|
let topNode: ASDisplayNode
|
|
let bottomNode: ASDisplayNode
|
|
private let topNavigationBar: NavigationBar?
|
|
private let bottomNavigationBar: NavigationBar?
|
|
private let dimNode: ASDisplayNode
|
|
private let shadowNode: ASImageNode
|
|
private let customTransitionNode: CustomNavigationTransitionNode?
|
|
|
|
private let inlineNavigationBarTransition: Bool
|
|
|
|
private(set) var animatingCompletion = false
|
|
private var currentCompletion: (() -> Void)?
|
|
private var didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)?
|
|
|
|
private var frameRateLink: SharedDisplayLinkDriver.Link?
|
|
|
|
init(transition: NavigationTransition, isInteractive: Bool, isFlat: Bool, container: NavigationContainer, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) {
|
|
self.transition = transition
|
|
self.isInteractive = isInteractive
|
|
self.isFlat = isFlat
|
|
self.container = container
|
|
self.didUpdateProgress = didUpdateProgress
|
|
self.topNode = topNode
|
|
self.bottomNode = bottomNode
|
|
self.topNavigationBar = topNavigationBar
|
|
self.bottomNavigationBar = bottomNavigationBar
|
|
self.dimNode = ASDisplayNode()
|
|
self.dimNode.backgroundColor = UIColor.black
|
|
self.shadowNode = ASImageNode()
|
|
self.shadowNode.displaysAsynchronously = false
|
|
self.shadowNode.image = shadowImage
|
|
|
|
if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar {
|
|
if let customTransitionNode = topNavigationBar.makeCustomTransitionNode?(bottomNavigationBar, isInteractive) {
|
|
self.inlineNavigationBarTransition = false
|
|
customTransitionNode.setup(topNavigationBar: topNavigationBar, bottomNavigationBar: bottomNavigationBar)
|
|
self.customTransitionNode = customTransitionNode
|
|
} else if let customTransitionNode = bottomNavigationBar.makeCustomTransitionNode?(topNavigationBar, isInteractive) {
|
|
self.inlineNavigationBarTransition = false
|
|
customTransitionNode.setup(topNavigationBar: topNavigationBar, bottomNavigationBar: bottomNavigationBar)
|
|
self.customTransitionNode = customTransitionNode
|
|
} else if !topNavigationBar.isHidden, !bottomNavigationBar.isHidden, topNavigationBar.canTransitionInline, bottomNavigationBar.canTransitionInline, topNavigationBar.item?.leftBarButtonItem == nil {
|
|
var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container.view)
|
|
var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container.view)
|
|
topFrame.origin.x = 0.0
|
|
bottomFrame.origin.x = 0.0
|
|
self.inlineNavigationBarTransition = true
|
|
self.customTransitionNode = nil
|
|
} else {
|
|
self.inlineNavigationBarTransition = false
|
|
self.customTransitionNode = nil
|
|
}
|
|
} else {
|
|
self.inlineNavigationBarTransition = false
|
|
self.customTransitionNode = nil
|
|
}
|
|
|
|
switch transition {
|
|
case .Push:
|
|
self.container.addSubnode(topNode)
|
|
case .Pop:
|
|
if topNode.supernode == self.container {
|
|
self.container.insertSubnode(bottomNode, belowSubnode: topNode)
|
|
} else {
|
|
self.container.addSubnode(topNode)
|
|
}
|
|
}
|
|
|
|
if !self.isFlat {
|
|
self.container.insertSubnode(self.dimNode, belowSubnode: topNode)
|
|
self.container.insertSubnode(self.shadowNode, belowSubnode: self.dimNode)
|
|
}
|
|
if let customTransitionNode = self.customTransitionNode {
|
|
self.container.addSubnode(customTransitionNode)
|
|
}
|
|
|
|
self.maybeCreateNavigationBarTransition()
|
|
self.updateProgress(0.0, transition: .immediate, completion: {})
|
|
|
|
self.frameRateLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { _ in })
|
|
}
|
|
|
|
required init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
func updateProgress(_ progress: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
|
self._progress = progress
|
|
|
|
let position: CGFloat
|
|
switch self.transition {
|
|
case .Push:
|
|
position = 1.0 - progress
|
|
case .Pop:
|
|
position = progress
|
|
}
|
|
|
|
var dimInset: CGFloat = 0.0
|
|
if let bottomNavigationBar = self.bottomNavigationBar , self.inlineNavigationBarTransition {
|
|
if self.bottomNavigationBar?.isBackgroundVisible == false || self.topNavigationBar?.isBackgroundVisible == false {
|
|
|
|
} else {
|
|
dimInset = bottomNavigationBar.frame.maxY
|
|
}
|
|
}
|
|
|
|
let containerSize = self.container.bounds.size
|
|
|
|
let topFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(position * containerSize.width), y: 0.0), size: containerSize)
|
|
let bottomFrame = CGRect(origin: CGPoint(x: self.isFlat ? -floorToScreenPixels((1.0 - position) * containerSize.width) : ((position - 1.0) * containerSize.width * 0.3), y: 0.0), size: containerSize)
|
|
|
|
var canInvokeCompletion = false
|
|
var hadEarlyCompletion = false
|
|
transition.updateFrame(node: self.topNode, frame: topFrame, completion: { _ in
|
|
if canInvokeCompletion {
|
|
completion()
|
|
} else {
|
|
hadEarlyCompletion = true
|
|
}
|
|
})
|
|
canInvokeCompletion = true
|
|
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX + self.container.overflowInset), height: self.container.bounds.size.height - dimInset)))
|
|
transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: self.dimNode.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset)))
|
|
transition.updateAlpha(node: self.dimNode, alpha: (1.0 - position) * 0.15)
|
|
transition.updateAlpha(node: self.shadowNode, alpha: (1.0 - position) * 0.9)
|
|
|
|
transition.updateFrame(node: self.bottomNode, frame: bottomFrame)
|
|
|
|
self.updateNavigationBarTransition(transition: transition)
|
|
|
|
if let customTransitionNode = self.customTransitionNode {
|
|
customTransitionNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: containerSize.width, height: containerSize.height))
|
|
customTransitionNode.update(containerSize: containerSize, fraction: position, transition: transition)
|
|
}
|
|
|
|
self.didUpdateProgress?(self.progress, transition, topFrame, bottomFrame)
|
|
|
|
if hadEarlyCompletion {
|
|
completion()
|
|
}
|
|
}
|
|
|
|
private func updateNavigationBarTransition(transition: ContainedViewLayoutTransition) {
|
|
if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar, self.inlineNavigationBarTransition {
|
|
let position: CGFloat
|
|
switch self.transition {
|
|
case .Push:
|
|
position = 1.0 - progress
|
|
case .Pop:
|
|
position = progress
|
|
}
|
|
|
|
transition.animateView {
|
|
topNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: bottomNavigationBar, transition: self.transition, role: .top, progress: position)
|
|
bottomNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: topNavigationBar, transition: self.transition, role: .bottom, progress: position)
|
|
}
|
|
}
|
|
}
|
|
|
|
func maybeCreateNavigationBarTransition() {
|
|
if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar, self.inlineNavigationBarTransition {
|
|
let position: CGFloat
|
|
switch self.transition {
|
|
case .Push:
|
|
position = 1.0 - progress
|
|
case .Pop:
|
|
position = progress
|
|
}
|
|
|
|
topNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: bottomNavigationBar, transition: self.transition, role: .top, progress: position)
|
|
bottomNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: topNavigationBar, transition: self.transition, role: .bottom, progress: position)
|
|
}
|
|
}
|
|
|
|
func endNavigationBarTransition() {
|
|
if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar, self.inlineNavigationBarTransition {
|
|
topNavigationBar.transitionState = nil
|
|
bottomNavigationBar.transitionState = nil
|
|
}
|
|
}
|
|
|
|
func animateCancel(_ completion: @escaping () -> ()) {
|
|
self.currentCompletion = completion
|
|
|
|
self.updateProgress(0.0, transition: .animated(duration: 0.1, curve: .easeInOut), completion: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
switch strongSelf.transition {
|
|
case .Push:
|
|
strongSelf.topNode.removeFromSupernode()
|
|
case .Pop:
|
|
strongSelf.bottomNode.removeFromSupernode()
|
|
}
|
|
|
|
strongSelf.dimNode.removeFromSupernode()
|
|
strongSelf.shadowNode.removeFromSupernode()
|
|
|
|
strongSelf.customTransitionNode?.restore()
|
|
strongSelf.customTransitionNode?.removeFromSupernode()
|
|
|
|
strongSelf.endNavigationBarTransition()
|
|
|
|
if let currentCompletion = strongSelf.currentCompletion {
|
|
strongSelf.currentCompletion = nil
|
|
currentCompletion()
|
|
}
|
|
})
|
|
}
|
|
|
|
func complete() {
|
|
self.animatingCompletion = true
|
|
|
|
self._progress = 1.0
|
|
|
|
self.dimNode.removeFromSupernode()
|
|
self.shadowNode.removeFromSupernode()
|
|
|
|
self.customTransitionNode?.restore()
|
|
self.customTransitionNode?.removeFromSupernode()
|
|
|
|
self.endNavigationBarTransition()
|
|
|
|
if let currentCompletion = self.currentCompletion {
|
|
self.currentCompletion = nil
|
|
currentCompletion()
|
|
}
|
|
}
|
|
|
|
func performCompletion(completion: @escaping () -> ()) {
|
|
self.updateProgress(1.0, transition: .immediate, completion: { [weak self] in
|
|
if let strongSelf = self {
|
|
strongSelf.dimNode.removeFromSupernode()
|
|
strongSelf.shadowNode.removeFromSupernode()
|
|
|
|
strongSelf.customTransitionNode?.restore()
|
|
strongSelf.customTransitionNode?.removeFromSupernode()
|
|
|
|
strongSelf.endNavigationBarTransition()
|
|
|
|
if let currentCompletion = strongSelf.currentCompletion {
|
|
strongSelf.currentCompletion = nil
|
|
currentCompletion()
|
|
}
|
|
}
|
|
completion()
|
|
})
|
|
}
|
|
|
|
func animateCompletion(_ velocity: CGFloat, completion: @escaping () -> ()) {
|
|
self.animatingCompletion = true
|
|
let distance = (1.0 - self.progress) * self.container.bounds.size.width
|
|
self.currentCompletion = completion
|
|
let f = {
|
|
self.dimNode.removeFromSupernode()
|
|
self.shadowNode.removeFromSupernode()
|
|
|
|
self.customTransitionNode?.restore()
|
|
self.customTransitionNode?.removeFromSupernode()
|
|
|
|
self.endNavigationBarTransition()
|
|
|
|
if let currentCompletion = self.currentCompletion {
|
|
self.currentCompletion = nil
|
|
currentCompletion()
|
|
}
|
|
}
|
|
|
|
if abs(velocity) < CGFloat.ulpOfOne && abs(self.progress) < CGFloat.ulpOfOne {
|
|
self.updateProgress(1.0, transition: .animated(duration: 0.5, curve: .spring), completion: {
|
|
f()
|
|
})
|
|
} else {
|
|
self.updateProgress(1.0, transition: .animated(duration: Double(max(0.05, min(0.2, abs(distance / velocity)))), curve: .easeInOut), completion: {
|
|
f()
|
|
})
|
|
}
|
|
}
|
|
}
|