mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
444 lines
16 KiB
Swift
444 lines
16 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import SwiftSignalKit
|
|
|
|
private let orientationChangeDuration: Double = UIDevice.current.userInterfaceIdiom == .pad ? 0.4 : 0.3
|
|
|
|
private let defaultOrientations: UIInterfaceOrientationMask = {
|
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
|
return .all
|
|
} else {
|
|
return .allButUpsideDown
|
|
}
|
|
}()
|
|
|
|
public enum WindowUserInterfaceStyle {
|
|
case light
|
|
case dark
|
|
|
|
@available(iOS 12.0, *)
|
|
public init(style: UIUserInterfaceStyle) {
|
|
switch style {
|
|
case .light, .unspecified:
|
|
self = .light
|
|
case .dark:
|
|
self = .dark
|
|
@unknown default:
|
|
self = .dark
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class PreviewingHostViewDelegate {
|
|
public let controllerForLocation: (UIView, CGPoint) -> (UIViewController, CGRect)?
|
|
public let commitController: (UIViewController) -> Void
|
|
|
|
public init(controllerForLocation: @escaping (UIView, CGPoint) -> (UIViewController, CGRect)?, commitController: @escaping (UIViewController) -> Void) {
|
|
self.controllerForLocation = controllerForLocation
|
|
self.commitController = commitController
|
|
}
|
|
}
|
|
|
|
public protocol PreviewingHostView {
|
|
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
|
|
var previewingDelegate: PreviewingHostViewDelegate? { get }
|
|
}
|
|
|
|
private func tracePreviewingHostView(view: UIView, point: CGPoint) -> (UIView & PreviewingHostView, CGPoint)? {
|
|
if let view = view as? UIView & PreviewingHostView {
|
|
return (view, point)
|
|
}
|
|
if let superview = view.superview {
|
|
if let result = tracePreviewingHostView(view: superview, point: superview.convert(point, from: view)) {
|
|
return result
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private final class WindowRootViewControllerView: UIView {
|
|
override var frame: CGRect {
|
|
get {
|
|
return super.frame
|
|
} set(value) {
|
|
var value = value
|
|
value.size.height += value.minY
|
|
value.origin.y = 0.0
|
|
super.frame = value
|
|
}
|
|
}
|
|
}
|
|
|
|
private final class WindowRootViewController: UIViewController, UIViewControllerPreviewingDelegate {
|
|
private var voiceOverStatusObserver: AnyObject?
|
|
private var registeredForPreviewing = false
|
|
|
|
var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)?
|
|
var transitionToSize: ((CGSize, Double) -> Void)?
|
|
|
|
private var _systemUserInterfaceStyle = ValuePromise<WindowUserInterfaceStyle>(ignoreRepeated: true)
|
|
var systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError> {
|
|
return self._systemUserInterfaceStyle.get()
|
|
}
|
|
|
|
var orientations: UIInterfaceOrientationMask = defaultOrientations {
|
|
didSet {
|
|
if oldValue != self.orientations {
|
|
if self.orientations == .portrait {
|
|
if UIDevice.current.orientation != .portrait {
|
|
let value = UIInterfaceOrientation.portrait.rawValue
|
|
UIDevice.current.setValue(value, forKey: "orientation")
|
|
}
|
|
} else {
|
|
UIViewController.attemptRotationToDeviceOrientation()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var gestureEdges: UIRectEdge = [] {
|
|
didSet {
|
|
if oldValue != self.gestureEdges {
|
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
|
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var prefersOnScreenNavigationHidden: Bool = false {
|
|
didSet {
|
|
if oldValue != self.prefersOnScreenNavigationHidden {
|
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
|
self.setNeedsUpdateOfHomeIndicatorAutoHidden()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
|
return .default
|
|
}
|
|
|
|
override var prefersStatusBarHidden: Bool {
|
|
return false
|
|
}
|
|
|
|
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
|
return orientations
|
|
}
|
|
|
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
if #available(iOS 12.0, *) {
|
|
self._systemUserInterfaceStyle.set(WindowUserInterfaceStyle(style: self.traitCollection.userInterfaceStyle))
|
|
}
|
|
}
|
|
|
|
init() {
|
|
super.init(nibName: nil, bundle: nil)
|
|
|
|
self.extendedLayoutIncludesOpaqueBars = true
|
|
|
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
|
self.voiceOverStatusObserver = NotificationCenter.default.addObserver(forName: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil, queue: OperationQueue.main, using: { [weak self] _ in
|
|
if let strongSelf = self {
|
|
strongSelf.updatePreviewingRegistration()
|
|
}
|
|
})
|
|
}
|
|
|
|
if #available(iOS 13.0, *) {
|
|
self._systemUserInterfaceStyle.set(WindowUserInterfaceStyle(style: self.traitCollection.userInterfaceStyle))
|
|
} else {
|
|
self._systemUserInterfaceStyle.set(.light)
|
|
}
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
if let voiceOverStatusObserver = self.voiceOverStatusObserver {
|
|
NotificationCenter.default.removeObserver(voiceOverStatusObserver)
|
|
}
|
|
}
|
|
|
|
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
|
|
return self.gestureEdges
|
|
}
|
|
|
|
override var prefersHomeIndicatorAutoHidden: Bool {
|
|
return self.prefersOnScreenNavigationHidden
|
|
}
|
|
|
|
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
|
super.viewWillTransition(to: size, with: coordinator)
|
|
UIView.performWithoutAnimation {
|
|
self.transitionToSize?(size, coordinator.transitionDuration)
|
|
}
|
|
}
|
|
|
|
override func loadView() {
|
|
self.view = WindowRootViewControllerView()
|
|
self.view.isOpaque = false
|
|
self.view.backgroundColor = nil
|
|
|
|
self.updatePreviewingRegistration()
|
|
}
|
|
|
|
private var previewingContext: AnyObject?
|
|
|
|
private func updatePreviewingRegistration() {
|
|
}
|
|
|
|
private weak var previousPreviewingHostView: (UIView & PreviewingHostView)?
|
|
|
|
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
|
|
if UIAccessibility.isVoiceOverRunning {
|
|
return nil
|
|
}
|
|
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
|
guard let result = self.view.hitTest(location, with: nil) else {
|
|
return nil
|
|
}
|
|
if let (result, resultPoint) = tracePreviewingHostView(view: result, point: self.view.convert(location, to: result)), let delegate = result.previewingDelegate {
|
|
self.previousPreviewingHostView = result
|
|
if let (controller, rect) = delegate.controllerForLocation(previewingContext.sourceView, resultPoint) {
|
|
previewingContext.sourceRect = rect
|
|
return controller
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
|
|
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
|
if let previousPreviewingHostView = self.previousPreviewingHostView, let delegate = previousPreviewingHostView.previewingDelegate {
|
|
delegate.commitController(viewControllerToCommit)
|
|
}
|
|
self.previousPreviewingHostView = nil
|
|
}
|
|
}
|
|
|
|
override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
|
|
super.present(viewControllerToPresent, animated: flag, completion: completion)
|
|
}
|
|
}
|
|
|
|
private final class NativeWindow: UIWindow, WindowHost {
|
|
var updateSize: ((CGSize) -> Void)?
|
|
var layoutSubviewsEvent: (() -> Void)?
|
|
var updateIsUpdatingOrientationLayout: ((Bool) -> Void)?
|
|
var updateToInterfaceOrientation: ((UIInterfaceOrientation) -> Void)?
|
|
var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
|
|
var presentControllerInGlobalOverlay: ((_ controller: ContainableController) -> Void)?
|
|
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
|
|
var presentNativeImpl: ((UIViewController) -> Void)?
|
|
var invalidateDeferScreenEdgeGestureImpl: (() -> Void)?
|
|
var invalidatePrefersOnScreenNavigationHiddenImpl: (() -> Void)?
|
|
var invalidateSupportedOrientationsImpl: (() -> Void)?
|
|
var cancelInteractiveKeyboardGesturesImpl: (() -> Void)?
|
|
var forEachControllerImpl: (((ContainableController) -> Void) -> Void)?
|
|
var getAccessibilityElementsImpl: (() -> [Any]?)?
|
|
|
|
override var frame: CGRect {
|
|
get {
|
|
return super.frame
|
|
} set(value) {
|
|
let sizeUpdated = super.frame.size != value.size
|
|
|
|
var frameTransition: ContainedViewLayoutTransition = .immediate
|
|
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
|
let duration = UIView.inheritedAnimationDuration
|
|
if !duration.isZero {
|
|
frameTransition = .animated(duration: duration, curve: .easeInOut)
|
|
}
|
|
}
|
|
if sizeUpdated, case let .animated(duration, curve) = frameTransition {
|
|
let previousFrame = super.frame
|
|
super.frame = value
|
|
self.layer.animateFrame(from: previousFrame, to: value, duration: duration, timingFunction: curve.timingFunction)
|
|
} else {
|
|
super.frame = value
|
|
}
|
|
|
|
if sizeUpdated {
|
|
self.updateSize?(value.size)
|
|
}
|
|
}
|
|
}
|
|
|
|
override var bounds: CGRect {
|
|
get {
|
|
return super.bounds
|
|
}
|
|
set(value) {
|
|
let sizeUpdated = super.bounds.size != value.size
|
|
super.bounds = value
|
|
|
|
if sizeUpdated {
|
|
self.updateSize?(value.size)
|
|
}
|
|
}
|
|
}
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
|
|
if let gestureRecognizers = self.gestureRecognizers {
|
|
for recognizer in gestureRecognizers {
|
|
recognizer.delaysTouchesBegan = false
|
|
}
|
|
}
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
|
|
self.layoutSubviewsEvent?()
|
|
}
|
|
|
|
override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) {
|
|
self.updateIsUpdatingOrientationLayout?(true)
|
|
super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3)
|
|
self.updateIsUpdatingOrientationLayout?(false)
|
|
|
|
let orientation = UIInterfaceOrientation(rawValue: Int(arg1)) ?? .unknown
|
|
self.updateToInterfaceOrientation?(orientation)
|
|
}
|
|
|
|
func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) {
|
|
self.presentController?(controller, level, blockInteraction, completion)
|
|
}
|
|
|
|
func presentInGlobalOverlay(_ controller: ContainableController) {
|
|
self.presentControllerInGlobalOverlay?(controller)
|
|
}
|
|
|
|
func presentNative(_ controller: UIViewController) {
|
|
self.presentNativeImpl?(controller)
|
|
}
|
|
|
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
return self.hitTestImpl?(point, event)
|
|
}
|
|
|
|
func invalidateDeferScreenEdgeGestures() {
|
|
self.invalidateDeferScreenEdgeGestureImpl?()
|
|
}
|
|
|
|
func invalidatePrefersOnScreenNavigationHidden() {
|
|
self.invalidatePrefersOnScreenNavigationHiddenImpl?()
|
|
}
|
|
|
|
func invalidateSupportedOrientations() {
|
|
self.invalidateSupportedOrientationsImpl?()
|
|
}
|
|
|
|
func cancelInteractiveKeyboardGestures() {
|
|
self.cancelInteractiveKeyboardGesturesImpl?()
|
|
}
|
|
|
|
func forEachController(_ f: (ContainableController) -> Void) {
|
|
self.forEachControllerImpl?(f)
|
|
}
|
|
}
|
|
|
|
public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) {
|
|
let window = NativeWindow(frame: UIScreen.main.bounds)
|
|
|
|
let rootViewController = WindowRootViewController()
|
|
window.rootViewController = rootViewController
|
|
rootViewController.viewWillAppear(false)
|
|
rootViewController.view.frame = CGRect(origin: CGPoint(), size: window.bounds.size)
|
|
rootViewController.viewDidAppear(false)
|
|
|
|
let hostView = WindowHostView(containerView: rootViewController.view, eventView: window, isRotating: {
|
|
return window.isRotating()
|
|
}, systemUserInterfaceStyle: rootViewController.systemUserInterfaceStyle, updateSupportedInterfaceOrientations: { orientations in
|
|
rootViewController.orientations = orientations
|
|
}, updateDeferScreenEdgeGestures: { edges in
|
|
rootViewController.gestureEdges = edges
|
|
}, updatePrefersOnScreenNavigationHidden: { value in
|
|
rootViewController.prefersOnScreenNavigationHidden = value
|
|
})
|
|
|
|
rootViewController.transitionToSize = { [weak hostView] size, duration in
|
|
hostView?.updateSize?(size, duration)
|
|
}
|
|
|
|
window.updateSize = { _ in
|
|
}
|
|
|
|
window.layoutSubviewsEvent = { [weak hostView] in
|
|
hostView?.layoutSubviews?()
|
|
}
|
|
|
|
window.updateIsUpdatingOrientationLayout = { [weak hostView] value in
|
|
hostView?.isUpdatingOrientationLayout = value
|
|
}
|
|
|
|
window.updateToInterfaceOrientation = { [weak hostView] orientation in
|
|
hostView?.updateToInterfaceOrientation?(orientation)
|
|
}
|
|
|
|
window.presentController = { [weak hostView] controller, level, blockInteraction, completion in
|
|
hostView?.present?(controller, level, blockInteraction, completion)
|
|
}
|
|
|
|
window.presentControllerInGlobalOverlay = { [weak hostView] controller in
|
|
hostView?.presentInGlobalOverlay?(controller)
|
|
}
|
|
|
|
window.presentNativeImpl = { [weak hostView] controller in
|
|
hostView?.presentNative?(controller)
|
|
}
|
|
|
|
hostView.nativeController = { [weak rootViewController] in
|
|
return rootViewController
|
|
}
|
|
|
|
window.hitTestImpl = { [weak hostView] point, event in
|
|
return hostView?.hitTest?(point, event)
|
|
}
|
|
|
|
window.invalidateDeferScreenEdgeGestureImpl = { [weak hostView] in
|
|
return hostView?.invalidateDeferScreenEdgeGesture?()
|
|
}
|
|
|
|
window.invalidatePrefersOnScreenNavigationHiddenImpl = { [weak hostView] in
|
|
return hostView?.invalidatePrefersOnScreenNavigationHidden?()
|
|
}
|
|
|
|
window.invalidateSupportedOrientationsImpl = { [weak hostView] in
|
|
return hostView?.invalidateSupportedOrientations?()
|
|
}
|
|
|
|
window.cancelInteractiveKeyboardGesturesImpl = { [weak hostView] in
|
|
hostView?.cancelInteractiveKeyboardGestures?()
|
|
}
|
|
|
|
window.forEachControllerImpl = { [weak hostView] f in
|
|
hostView?.forEachController?(f)
|
|
}
|
|
|
|
window.getAccessibilityElementsImpl = { [weak hostView] in
|
|
return hostView?.getAccessibilityElements?()
|
|
}
|
|
|
|
rootViewController.presentController = { [weak hostView] controller, level, animated, completion in
|
|
if let hostView = hostView {
|
|
hostView.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level, false, completion ?? {})
|
|
completion?()
|
|
}
|
|
}
|
|
|
|
return (window, hostView)
|
|
}
|