mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
315 lines
12 KiB
Swift
315 lines
12 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
|
|
public func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) -> Bool {
|
|
guard let window = view.window else {
|
|
return false
|
|
}
|
|
if view.isHidden || view.alpha == 0.0 {
|
|
return false
|
|
}
|
|
if view.superview === window {
|
|
return true
|
|
} else if let superview = view.superview {
|
|
if initial && view.frame.minY >= superview.frame.height {
|
|
return false
|
|
} else {
|
|
return isViewVisibleInHierarchy(superview, false)
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
public final class HierarchyTrackingNode: ASDisplayNode {
|
|
public var updated: (Bool) -> Void
|
|
|
|
public init(_ f: @escaping (Bool) -> Void = { _ in }) {
|
|
self.updated = f
|
|
|
|
super.init()
|
|
|
|
self.isLayerBacked = true
|
|
}
|
|
|
|
override public func didEnterHierarchy() {
|
|
super.didEnterHierarchy()
|
|
|
|
self.updated(true)
|
|
}
|
|
|
|
override public func didExitHierarchy() {
|
|
super.didExitHierarchy()
|
|
|
|
self.updated(false)
|
|
}
|
|
}
|
|
|
|
final class GlobalOverlayPresentationContext {
|
|
private let statusBarHost: StatusBarHost?
|
|
private weak var parentView: UIView?
|
|
|
|
private(set) var controllers: [ContainableController] = []
|
|
|
|
private var globalPortalViews: [GlobalPortalView] = []
|
|
|
|
private var presentationDisposables = DisposableSet()
|
|
private var layout: ContainerViewLayout?
|
|
|
|
private var ready: Bool {
|
|
return self.currentPresentationView(underStatusBar: false) != nil && self.layout != nil
|
|
}
|
|
|
|
init(statusBarHost: StatusBarHost?, parentView: UIView) {
|
|
self.statusBarHost = statusBarHost
|
|
self.parentView = parentView
|
|
}
|
|
|
|
private var currentTrackingNode: HierarchyTrackingNode?
|
|
|
|
private func currentPresentationView(underStatusBar: Bool) -> UIView? {
|
|
if let statusBarHost = self.statusBarHost {
|
|
if let keyboardWindow = statusBarHost.keyboardWindow, let keyboardView = statusBarHost.keyboardView, !keyboardView.frame.height.isZero, isViewVisibleInHierarchy(keyboardView) {
|
|
var updateTrackingNode = false
|
|
if let trackingNode = self.currentTrackingNode {
|
|
if trackingNode.layer.superlayer !== keyboardView.layer {
|
|
updateTrackingNode = true
|
|
}
|
|
} else {
|
|
updateTrackingNode = true
|
|
}
|
|
|
|
if updateTrackingNode {
|
|
/*self.currentTrackingNode?.removeFromSupernode()
|
|
let trackingNode = HierarchyTrackingNode({ [weak self] value in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if !value {
|
|
strongSelf.addViews(justMove: true)
|
|
}
|
|
})
|
|
|
|
self.currentTrackingNode = trackingNode
|
|
keyboardView.layer.addSublayer(trackingNode.layer)*/
|
|
}
|
|
return keyboardWindow
|
|
} else {
|
|
if let view = self.parentView {
|
|
return view
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func present(_ controller: ContainableController) {
|
|
let controllerReady = controller.ready.get()
|
|
|> filter({ $0 })
|
|
|> take(1)
|
|
|> deliverOnMainQueue
|
|
|> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true))
|
|
|
|
var underStatusBar = false
|
|
if let controller = controller as? ViewController {
|
|
if case .Hide = controller.statusBar.statusBarStyle {
|
|
underStatusBar = true
|
|
}
|
|
}
|
|
if let presentationView = self.currentPresentationView(underStatusBar: underStatusBar), let initialLayout = self.layout {
|
|
if initialLayout.metrics.widthClass == .regular {
|
|
controller.view.frame = CGRect(origin: CGPoint(x: presentationView.bounds.width - initialLayout.size.width, y: 0.0), size: initialLayout.size)
|
|
} else {
|
|
controller.view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: initialLayout.size)
|
|
}
|
|
controller.containerLayoutUpdated(initialLayout, transition: .immediate)
|
|
|
|
self.presentationDisposables.add(controllerReady.start(next: { [weak self] _ in
|
|
if let strongSelf = self {
|
|
if strongSelf.controllers.contains(where: { $0 === controller }) {
|
|
return
|
|
}
|
|
|
|
strongSelf.controllers.append(controller)
|
|
if let view = strongSelf.currentPresentationView(underStatusBar: underStatusBar), let layout = strongSelf.layout {
|
|
(controller as? UIViewController)?.navigation_setDismiss({ [weak controller] in
|
|
if let strongSelf = self, let controller = controller {
|
|
strongSelf.dismiss(controller)
|
|
}
|
|
}, rootController: nil)
|
|
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true)
|
|
if layout != initialLayout {
|
|
if layout.metrics.widthClass == .regular {
|
|
controller.view.frame = CGRect(origin: CGPoint(x: view.bounds.width - layout.size.width, y: 0.0), size: layout.size)
|
|
} else {
|
|
controller.view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)
|
|
}
|
|
view.addSubview(controller.view)
|
|
controller.containerLayoutUpdated(layout, transition: .immediate)
|
|
} else {
|
|
view.addSubview(controller.view)
|
|
}
|
|
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false)
|
|
controller.viewWillAppear(false)
|
|
controller.viewDidAppear(false)
|
|
}
|
|
}
|
|
}))
|
|
} else {
|
|
self.controllers.append(controller)
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.presentationDisposables.dispose()
|
|
}
|
|
|
|
private func dismiss(_ controller: ContainableController) {
|
|
if let index = self.controllers.firstIndex(where: { $0 === controller }) {
|
|
self.controllers.remove(at: index)
|
|
controller.viewWillDisappear(false)
|
|
controller.view.removeFromSuperview()
|
|
controller.viewDidDisappear(false)
|
|
}
|
|
}
|
|
|
|
public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
let wasReady = self.ready
|
|
self.layout = layout
|
|
|
|
if wasReady != self.ready {
|
|
self.readyChanged(wasReady: wasReady)
|
|
} else if self.ready {
|
|
for controller in self.controllers {
|
|
transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
|
controller.containerLayoutUpdated(layout, transition: transition)
|
|
}
|
|
|
|
for globalPortalView in self.globalPortalViews {
|
|
transition.updateFrame(view: globalPortalView.view, frame: CGRect(origin: CGPoint(), size: layout.size))
|
|
}
|
|
}
|
|
}
|
|
|
|
public func addGlobalPortalHostView(sourceView: PortalSourceView) {
|
|
guard let globalPortalView = GlobalPortalView(wasRemoved: { [weak self] globalPortalView in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if let index = strongSelf.globalPortalViews.firstIndex(where: { $0 === globalPortalView }) {
|
|
strongSelf.globalPortalViews.remove(at: index)
|
|
}
|
|
globalPortalView.view.removeFromSuperview()
|
|
}) else {
|
|
return
|
|
}
|
|
|
|
globalPortalView.view.isUserInteractionEnabled = false
|
|
self.globalPortalViews.append(globalPortalView)
|
|
|
|
sourceView.setGlobalPortal(view: globalPortalView)
|
|
|
|
if let presentationView = self.currentPresentationView(underStatusBar: true), let initialLayout = self.layout {
|
|
presentationView.addSubview(globalPortalView.view)
|
|
globalPortalView.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
|
|
}
|
|
}
|
|
|
|
private func readyChanged(wasReady: Bool) {
|
|
if !wasReady {
|
|
self.addViews(justMove: false)
|
|
} else {
|
|
self.removeViews()
|
|
}
|
|
}
|
|
|
|
private func addViews(justMove: Bool) {
|
|
if let layout = self.layout {
|
|
for controller in self.controllers {
|
|
var underStatusBar = false
|
|
if let controller = controller as? ViewController {
|
|
if case .Hide = controller.statusBar.statusBarStyle {
|
|
underStatusBar = true
|
|
}
|
|
}
|
|
if let view = self.currentPresentationView(underStatusBar: underStatusBar) {
|
|
if !justMove {
|
|
controller.viewWillAppear(false)
|
|
}
|
|
view.addSubview(controller.view)
|
|
if !justMove {
|
|
if layout.metrics.widthClass == .regular {
|
|
controller.view.frame = CGRect(origin: CGPoint(x: view.bounds.width - layout.size.width, y: 0.0), size: layout.size)
|
|
} else {
|
|
controller.view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)
|
|
}
|
|
controller.containerLayoutUpdated(layout, transition: .immediate)
|
|
controller.viewDidAppear(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !self.globalPortalViews.isEmpty, let view = self.currentPresentationView(underStatusBar: true) {
|
|
for globalPortalView in self.globalPortalViews {
|
|
view.addSubview(globalPortalView.view)
|
|
|
|
globalPortalView.view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func removeViews() {
|
|
for controller in self.controllers {
|
|
controller.viewWillDisappear(false)
|
|
controller.view.removeFromSuperview()
|
|
controller.viewDidDisappear(false)
|
|
}
|
|
|
|
for globalPortalView in self.globalPortalViews {
|
|
globalPortalView.view.removeFromSuperview()
|
|
}
|
|
}
|
|
|
|
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
for controller in self.controllers.reversed() {
|
|
if controller.isViewLoaded {
|
|
if let result = controller.view.hitTest(point, with: event) {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
|
|
if self.ready {
|
|
for controller in self.controllers {
|
|
controller.updateToInterfaceOrientation(orientation)
|
|
}
|
|
}
|
|
}
|
|
|
|
func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
|
|
var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
|
|
|
for controller in self.controllers {
|
|
mask = mask.intersection(controller.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
|
|
}
|
|
|
|
return mask
|
|
}
|
|
|
|
func combinedDeferScreenEdgeGestures() -> UIRectEdge {
|
|
var edges: UIRectEdge = []
|
|
|
|
for controller in self.controllers {
|
|
edges = edges.union(controller.deferScreenEdgeGestures)
|
|
}
|
|
|
|
return edges
|
|
}
|
|
}
|