Files
Swiftgram/submodules/Display/Source/WindowContent.swift
Kylmakalle fd86110711 Version 11.3.1
Fixes

fix localeWithStrings globally (#30)

Fix badge on zoomed devices. closes #9

Hide channel bottom panel closes #27

Another attempt to fix badge on some Zoomed devices

Force System Share sheet tg://sg/debug

fixes for device badge

New Crowdin updates (#34)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

Fix input panel hidden on selection (#31)

* added if check for selectionState != nil

* same order of subnodes

Revert "Fix input panel hidden on selection (#31)"

This reverts commit e8a8bb1496.

Fix input panel for channels Closes #37

Quickly share links with system's share menu

force tabbar when editing

increase height for correct animation

New translations sglocalizable.strings (Ukrainian) (#38)

Hide Post Story button

Fix 10.15.1

Fix archive option for long-tap

Enable in-app Safari

Disable some unsupported purchases

disableDeleteChatSwipeOption + refactor restart alert

Hide bot in suggestions list

Fix merge v11.0

Fix exceptions for safari webview controller

New Crowdin updates (#47)

* New translations sglocalizable.strings (Romanian)

* New translations sglocalizable.strings (French)

* New translations sglocalizable.strings (Spanish)

* New translations sglocalizable.strings (Afrikaans)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Catalan)

* New translations sglocalizable.strings (Czech)

* New translations sglocalizable.strings (Danish)

* New translations sglocalizable.strings (German)

* New translations sglocalizable.strings (Greek)

* New translations sglocalizable.strings (Finnish)

* New translations sglocalizable.strings (Hebrew)

* New translations sglocalizable.strings (Hungarian)

* New translations sglocalizable.strings (Italian)

* New translations sglocalizable.strings (Japanese)

* New translations sglocalizable.strings (Korean)

* New translations sglocalizable.strings (Dutch)

* New translations sglocalizable.strings (Norwegian)

* New translations sglocalizable.strings (Polish)

* New translations sglocalizable.strings (Portuguese)

* New translations sglocalizable.strings (Serbian (Cyrillic))

* New translations sglocalizable.strings (Swedish)

* New translations sglocalizable.strings (Turkish)

* New translations sglocalizable.strings (Vietnamese)

* New translations sglocalizable.strings (Indonesian)

* New translations sglocalizable.strings (Hindi)

* New translations sglocalizable.strings (Uzbek)

New Crowdin updates (#49)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Arabic)

New translations sglocalizable.strings (Russian) (#51)

Call confirmation

WIP Settings search

Settings Search

Localize placeholder

Update AccountUtils.swift

mark mutual contact

Align back context action to left

New Crowdin updates (#54)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Ukrainian)

Independent Playground app for simulator

New translations sglocalizable.strings (Ukrainian) (#55)

Playground UIKit base and controllers

Inject SwiftUI view with overflow to AsyncDisplayKit

Launch Playgound project on simulator

Create .swiftformat

Move Playground to example

Update .swiftformat

Init SwiftUIViewController

wip

New translations sglocalizable.strings (Chinese Traditional) (#57)

Xcode 16 fixes

Fix

New translations sglocalizable.strings (Italian) (#59)

New translations sglocalizable.strings (Chinese Simplified) (#63)

Force disable CallKit integration due to missing NSE Entitlement

Fix merge

Fix whole chat translator

Sweetpad config

Bump version

11.3.1 fixes

Mutual contact placement fix

Disable Video PIP swipe

Update versions.json

Fix PIP crash
2024-12-20 09:38:13 +02:00

1435 lines
73 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
private struct WindowLayout: Equatable {
let size: CGSize
let metrics: LayoutMetrics
let statusBarHeight: CGFloat?
let forceInCallStatusBarText: String?
let inputHeight: CGFloat?
let safeInsets: UIEdgeInsets
let onScreenNavigationHeight: CGFloat?
let upperKeyboardInputPositionBound: CGFloat?
let inVoiceOver: Bool
}
private struct UpdatingLayout {
var layout: WindowLayout
var transition: ContainedViewLayoutTransition
mutating func update(transition: ContainedViewLayoutTransition, override: Bool) {
var update = false
if case .immediate = self.transition {
update = true
} else if override {
update = true
}
if update {
self.transition = transition
}
}
mutating func update(size: CGSize, metrics: LayoutMetrics, safeInsets: UIEdgeInsets, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
self.update(transition: transition, override: overrideTransition)
self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver)
}
mutating func update(forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
self.update(transition: transition, override: overrideTransition)
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver)
}
mutating func update(statusBarHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
self.update(transition: transition, override: overrideTransition)
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver)
}
mutating func update(inputHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
self.update(transition: transition, override: overrideTransition)
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver)
}
mutating func update(safeInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
self.update(transition: transition, override: overrideTransition)
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver)
}
mutating func update(onScreenNavigationHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
self.update(transition: transition, override: overrideTransition)
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver)
}
mutating func update(upperKeyboardInputPositionBound: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
self.update(transition: transition, override: overrideTransition)
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver)
}
mutating func update(inVoiceOver: Bool) {
self.update(transition: transition, override: false)
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: inVoiceOver)
}
}
private let defaultStatusBarHeight: CGFloat = 20.0
private let statusBarHiddenInLandscape: Bool = UIDevice.current.userInterfaceIdiom == .phone
private func inputHeightOffsetForLayout(_ layout: WindowLayout) -> CGFloat {
if let inputHeight = layout.inputHeight, let upperBound = layout.upperKeyboardInputPositionBound {
return max(0.0, upperBound - (layout.size.height - inputHeight))
}
return 0.0
}
private func containedLayoutForWindowLayout(_ layout: WindowLayout, deviceMetrics: DeviceMetrics) -> ContainerViewLayout {
let resolvedStatusBarHeight: CGFloat?
if let statusBarHeight = layout.statusBarHeight {
if layout.forceInCallStatusBarText != nil {
resolvedStatusBarHeight = max(40.0, layout.safeInsets.top)
} else {
resolvedStatusBarHeight = statusBarHeight
}
} else {
resolvedStatusBarHeight = nil
}
var updatedInputHeight = layout.inputHeight
if let inputHeight = updatedInputHeight, let _ = layout.upperKeyboardInputPositionBound {
updatedInputHeight = inputHeight - inputHeightOffsetForLayout(layout)
}
let isLandscape = layout.size.width > layout.size.height
var resolvedSafeInsets = layout.safeInsets
if layout.safeInsets.left.isZero {
resolvedSafeInsets = deviceMetrics.safeInsets(inLandscape: isLandscape)
}
return ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 0.0, right: 0.0), safeInsets: resolvedSafeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil, inVoiceOver: layout.inVoiceOver)
}
public func doesViewTreeDisableInteractiveTransitionGestureRecognizer(_ view: UIView, keyboardOnly: Bool = false) -> Bool {
if view.disablesInteractiveTransitionGestureRecognizer && !keyboardOnly {
return true
}
if view.disablesInteractiveKeyboardGestureRecognizer {
return true
}
if let f = view.disablesInteractiveTransitionGestureRecognizerNow, f() {
return true
}
if let superview = view.superview {
return doesViewTreeDisableInteractiveTransitionGestureRecognizer(superview, keyboardOnly: keyboardOnly)
}
return false
}
public func getFirstResponderAndAccessoryHeight(_ view: UIView, _ accessoryHeight: CGFloat? = nil) -> (UIView?, CGFloat?) {
if view.isFirstResponder {
return (view, accessoryHeight)
} else {
var updatedAccessoryHeight = accessoryHeight
if let view = view as? WindowInputAccessoryHeightProvider {
updatedAccessoryHeight = view.getWindowInputAccessoryHeight()
}
for subview in view.subviews {
let (result, resultHeight) = getFirstResponderAndAccessoryHeight(subview, updatedAccessoryHeight)
if let result = result {
return (result, resultHeight)
}
}
return (nil, nil)
}
}
public final class WindowHostView {
public let containerView: UIView
public let eventView: UIView
public let isRotating: () -> Bool
public let systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError>
public let currentInterfaceOrientation: () -> UIInterfaceOrientation
let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void
let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void
let updatePrefersOnScreenNavigationHidden: (Bool) -> Void
var present: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
var presentInGlobalOverlay: ((_ controller: ContainableController) -> Void)?
var addGlobalPortalHostViewImpl: ((PortalSourceView) -> Void)?
var presentNative: ((UIViewController) -> Void)?
var nativeController: (() -> UIViewController?)?
var updateSize: ((CGSize, Double, UIInterfaceOrientation) -> Void)?
var layoutSubviews: (() -> Void)?
var updateToInterfaceOrientation: ((UIInterfaceOrientation) -> Void)?
var isUpdatingOrientationLayout = false
var hitTest: ((CGPoint, UIEvent?) -> UIView?)?
var invalidateDeferScreenEdgeGesture: (() -> Void)?
var invalidatePrefersOnScreenNavigationHidden: (() -> Void)?
var invalidateSupportedOrientations: (() -> Void)?
var cancelInteractiveKeyboardGestures: (() -> Void)?
var forEachController: (((ContainableController) -> Void) -> Void)?
var getAccessibilityElements: (() -> [Any]?)?
init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError>, currentInterfaceOrientation: @escaping () -> UIInterfaceOrientation, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePrefersOnScreenNavigationHidden: @escaping (Bool) -> Void) {
self.containerView = containerView
self.eventView = eventView
self.isRotating = isRotating
self.systemUserInterfaceStyle = systemUserInterfaceStyle
self.currentInterfaceOrientation = currentInterfaceOrientation
self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations
self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures
self.updatePrefersOnScreenNavigationHidden = updatePrefersOnScreenNavigationHidden
}
fileprivate var onScreenNavigationHeight: CGFloat? {
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
return self.eventView.safeAreaInsets.bottom.isLessThanOrEqualTo(0.0) ? nil : self.eventView.safeAreaInsets.bottom
} else {
return nil
}
}
}
public protocol WindowHost {
func forEachController(_ f: (ContainableController) -> Void)
func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void)
func presentInGlobalOverlay(_ controller: ContainableController)
func addGlobalPortalHostView(sourceView: PortalSourceView)
func invalidateDeferScreenEdgeGestures()
func invalidatePrefersOnScreenNavigationHidden()
func invalidateSupportedOrientations()
func cancelInteractiveKeyboardGestures()
}
public extension UIView {
var windowHost: WindowHost? {
if let window = self.window as? WindowHost {
return window
} else if let result = findWindow(self) {
return result
} else {
return nil
}
}
}
private func layoutMetricsForScreenSize(size: CGSize, orientation: UIInterfaceOrientation?) -> LayoutMetrics {
if size.width > 690.0 && size.height > 650.0 {
return LayoutMetrics(widthClass: .regular, heightClass: .regular, orientation: orientation)
} else {
return LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: orientation)
}
}
public final class WindowKeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let view = gestureRecognizer.view {
let location = touch.location(in: gestureRecognizer.view)
if location.y > view.bounds.height - 44.0 {
return false
}
}
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
public class Window1 {
public let hostView: WindowHostView
public let badgeView: UIImageView
private var deviceMetrics: DeviceMetrics
public let statusBarHost: StatusBarHost?
private let keyboardManager: KeyboardManager?
private let keyboardViewManager: KeyboardViewManager?
private var statusBarChangeObserver: AnyObject?
private var keyboardRotationChangeObserver: AnyObject?
private var keyboardFrameChangeObserver: AnyObject?
private var keyboardTypeChangeObserver: AnyObject?
private var voiceOverStatusObserver: AnyObject?
private var windowLayout: WindowLayout
private var updatingLayout: UpdatingLayout?
private var updatedContainerLayout: ContainerViewLayout?
private var upperKeyboardInputPositionBound: CGFloat?
private let presentationContext: PresentationContext
private let overlayPresentationContext: GlobalOverlayPresentationContext
private let topPresentationContext: PresentationContext
private var tracingStatusBarsInvalidated = false
private var shouldUpdateDeferScreenEdgeGestures = false
private var shouldInvalidatePrefersOnScreenNavigationHidden = false
private var shouldInvalidateSupportedOrientations = false
private var statusBarHidden = false
private var shouldNotAnimateLikelyKeyboardAutocorrectionSwitch: Bool = false
public private(set) var forceInCallStatusBarText: String? = nil
public var inCallNavigate: (() -> Void)?
private var debugTapCounter: (Double, Int) = (0.0, 0)
private var debugTapRecognizer: UITapGestureRecognizer?
public var debugAction: (() -> Void)? {
didSet {
if self.debugAction != nil {
if self.debugTapRecognizer == nil {
let debugTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.debugTapGesture(_:)))
self.debugTapRecognizer = debugTapRecognizer
self.hostView.containerView.addGestureRecognizer(debugTapRecognizer)
}
} else if let debugTapRecognizer = self.debugTapRecognizer {
self.debugTapRecognizer = nil
self.hostView.containerView.removeGestureRecognizer(debugTapRecognizer)
}
}
}
@objc private func debugTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
let timestamp = CACurrentMediaTime()
if self.debugTapCounter.0 < timestamp - 0.4 {
self.debugTapCounter.0 = timestamp
self.debugTapCounter.1 = 0
}
if self.debugTapCounter.0 >= timestamp - 0.4 {
self.debugTapCounter.0 = timestamp
self.debugTapCounter.1 += 1
}
if self.debugTapCounter.1 >= 10 {
self.debugTapCounter.1 = 0
self.debugAction?()
}
}
}
public let systemUserInterfaceStyle: Signal<WindowUserInterfaceStyle, NoError>
private var windowPanRecognizer: WindowPanRecognizer?
private let keyboardGestureRecognizerDelegate = WindowKeyboardGestureRecognizerDelegate()
private var keyboardGestureBeginLocation: CGPoint?
private var keyboardGestureAccessoryHeight: CGFloat?
private var keyboardTypeChangeTimer: SwiftSignalKit.Timer?
private var isInteractionBlocked = false
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
self.hostView = hostView
self.badgeView = UIImageView()
self.badgeView.image = UIImage(bundleImageName: "Components/AppBadge")
self.badgeView.isHidden = true
self.systemUserInterfaceStyle = hostView.systemUserInterfaceStyle
let boundsSize = self.hostView.eventView.bounds.size
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, scale: UIScreen.main.scale, statusBarHeight: statusBarHost?.statusBarFrame.height ?? 0.0, onScreenNavigationHeight: self.hostView.onScreenNavigationHeight)
self.statusBarHost = statusBarHost
let statusBarHeight: CGFloat
if let statusBarHost = statusBarHost {
statusBarHeight = statusBarHost.statusBarFrame.size.height
self.keyboardManager = KeyboardManager(host: statusBarHost)
self.keyboardViewManager = KeyboardViewManager(host: statusBarHost)
} else {
statusBarHeight = 0.0
self.keyboardManager = nil
self.keyboardViewManager = nil
}
let isLandscape = boundsSize.width > boundsSize.height
let safeInsets = self.deviceMetrics.safeInsets(inLandscape: isLandscape)
let onScreenNavigationHeight = self.deviceMetrics.onScreenNavigationHeight(inLandscape: isLandscape, systemOnScreenNavigationHeight: self.hostView.onScreenNavigationHeight)
let orientation: UIInterfaceOrientation = self.hostView.currentInterfaceOrientation()
self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(size: boundsSize, orientation: orientation), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil, inVoiceOver: UIAccessibility.isVoiceOverRunning)
self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate)
self.presentationContext = PresentationContext()
self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost, parentView: self.hostView.containerView)
self.topPresentationContext = PresentationContext()
self.presentationContext.topLevelSubview = { [weak self] in
guard let strongSelf = self else {
return nil
}
if let first = strongSelf.topPresentationContext.controllers.first {
return first.0.displayNode.view
}
if let first = strongSelf._topLevelOverlayControllers.first {
return first.view
}
return nil
}
self.presentationContext.updateIsInteractionBlocked = { [weak self] value in
self?.isInteractionBlocked = value
}
let updateOpaqueOverlays: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf._rootController?.displayNode.accessibilityElementsHidden = strongSelf.presentationContext.hasOpaqueOverlay || strongSelf.topPresentationContext.hasOpaqueOverlay
}
self.presentationContext.updateHasOpaqueOverlay = { value in
updateOpaqueOverlays()
}
self.topPresentationContext.updateHasOpaqueOverlay = { value in
updateOpaqueOverlays()
}
self.hostView.present = { [weak self] controller, level, blockInteraction, completion in
self?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
}
self.hostView.presentInGlobalOverlay = { [weak self] controller in
self?.presentInGlobalOverlay(controller)
}
self.hostView.addGlobalPortalHostViewImpl = { [weak self] sourceView in
self?.addGlobalPortalHostView(sourceView: sourceView)
}
self.hostView.presentNative = { [weak self] controller in
self?.presentNative(controller)
}
self.hostView.updateSize = { [weak self] size, duration, orientation in
self?.updateSize(size, duration: duration, orientation: orientation)
}
self.hostView.layoutSubviews = { [weak self] in
self?.layoutSubviews(force: false)
}
self.hostView.updateToInterfaceOrientation = { [weak self] orientation in
self?.updateToInterfaceOrientation(orientation)
}
self.hostView.hitTest = { [weak self] point, event in
return self?.hitTest(point, with: event)
}
self.hostView.invalidateDeferScreenEdgeGesture = { [weak self] in
self?.invalidateDeferScreenEdgeGestures()
}
self.hostView.invalidatePrefersOnScreenNavigationHidden = { [weak self] in
self?.invalidatePrefersOnScreenNavigationHidden()
}
self.hostView.invalidateSupportedOrientations = { [weak self] in
self?.invalidateSupportedOrientations()
}
self.hostView.cancelInteractiveKeyboardGestures = { [weak self] in
self?.cancelInteractiveKeyboardGestures()
}
self.hostView.forEachController = { [weak self] f in
self?.forEachViewController({ controller in
f(controller)
return true
})
}
self.presentationContext.view = self.hostView.containerView
self.topPresentationContext.view = self.hostView.containerView
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate)
self.topPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate)
self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate)
self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: UIApplication.willChangeStatusBarFrameNotification, object: nil, queue: OperationQueue.main, using: { [weak self] notification in
if let strongSelf = self, strongSelf.statusBarHost != nil {
let statusBarHeight: CGFloat = max(defaultStatusBarHeight, (notification.userInfo?[UIApplication.statusBarFrameUserInfoKey] as? NSValue)?.cgRectValue.height ?? defaultStatusBarHeight)
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .easeInOut)
strongSelf.updateLayout { $0.update(statusBarHeight: statusBarHeight, transition: transition, overrideTransition: false) }
}
})
self.keyboardRotationChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name("UITextEffectsWindowDidRotateNotification"), object: nil, queue: nil, using: { [weak self] notification in
if let strongSelf = self {
if !strongSelf.hostView.isUpdatingOrientationLayout {
return
}
var keyboardHeight = max(0.0, strongSelf.keyboardManager?.getCurrentKeyboardHeight() ?? 0.0)
if strongSelf.deviceMetrics.type == .tablet, abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 {
keyboardHeight = max(0.0, keyboardHeight - 24.0)
}
//print("rotation keyboardHeight: \(keyboardHeight)")
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
if duration > Double.ulpOfOne {
duration = 0.5
}
let curve: UInt = (notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7
let transitionCurve: ContainedViewLayoutTransitionCurve
if curve == 7 {
transitionCurve = .spring
} else {
transitionCurve = .easeInOut
}
strongSelf.updateLayout { $0.update(inputHeight: keyboardHeight.isLessThanOrEqualTo(0.0) ? nil : keyboardHeight, transition: .animated(duration: duration, curve: transitionCurve), overrideTransition: false) }
}
})
self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: nil, using: { [weak self] notification in
if let strongSelf = self {
var isTablet = false
if case .regular = strongSelf.windowLayout.metrics.widthClass {
isTablet = true
}
var keyboardFrame: CGRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect()
if isTablet && keyboardFrame.isEmpty {
return
}
if #available(iOSApplicationExtension 14.2, iOS 14.2, *), UIAccessibility.prefersCrossFadeTransitions {
} else if let keyboardView = strongSelf.statusBarHost?.keyboardView {
if keyboardFrame.width.isEqual(to: keyboardView.bounds.width) && keyboardFrame.height.isEqual(to: keyboardView.bounds.height) && keyboardFrame.minX.isEqual(to: keyboardView.frame.minX) {
keyboardFrame.origin.y = keyboardView.frame.minY
}
}
var minKeyboardY: CGFloat?
if #available(iOSApplicationExtension 16.1, iOS 16.1, *), let screen = notification.object as? UIScreen, let keyboardFrameEnd = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
let fromCoordinateSpace = screen.coordinateSpace
let toCoordinateSpace: UICoordinateSpace = strongSelf.hostView.eventView
let convertedKeyboardFrameEnd = fromCoordinateSpace.convert(keyboardFrameEnd, to: toCoordinateSpace)
minKeyboardY = convertedKeyboardFrameEnd.minY
}
var windowedHeightDifference: CGFloat = 0.0
let screenHeight: CGFloat
var isWindowed = false
if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) {
let screenSize = UIScreen.main.bounds.size
var portraitScreenSize = UIScreen.main.bounds.size
if portraitScreenSize.width > portraitScreenSize.height {
portraitScreenSize = CGSize(width: portraitScreenSize.height, height: portraitScreenSize.width)
}
var portraitLayoutSize = strongSelf.windowLayout.size
if portraitLayoutSize.width > portraitLayoutSize.height {
portraitLayoutSize = CGSize(width: portraitLayoutSize.height, height: portraitLayoutSize.width)
}
if strongSelf.windowLayout.size.height != screenSize.height {
let heightDelta = screenSize.height - strongSelf.windowLayout.size.height
//if heightDelta > 0.0 && heightDelta < 200.0 {
isWindowed = true
windowedHeightDifference = heightDelta / 2.0
//}
}
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
if isWindowed, let _ = minKeyboardY {
screenHeight = strongSelf.windowLayout.size.height
} else {
screenHeight = UIScreen.main.bounds.height
}
} else {
screenHeight = strongSelf.windowLayout.size.height
}
} else {
if let _ = minKeyboardY {
screenHeight = strongSelf.windowLayout.size.height
} else {
if keyboardFrame.minX > 0.0 {
screenHeight = UIScreen.main.bounds.height
} else {
screenHeight = UIScreen.main.bounds.width
}
}
}
var keyboardHeight: CGFloat
if keyboardFrame.isEmpty || keyboardFrame.maxY < screenHeight {
if isWindowed || (isTablet && screenHeight - keyboardFrame.maxY < 5.0) {
if let minKeyboardY {
keyboardFrame.origin.y = minKeyboardY
}
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
if isWindowed && !keyboardHeight.isZero, minKeyboardY == nil {
keyboardHeight = max(0.0, keyboardHeight - windowedHeightDifference)
}
} else {
keyboardHeight = 0.0
}
} else {
if let minKeyboardY {
keyboardFrame.origin.y = minKeyboardY
}
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
if isWindowed && !keyboardHeight.isZero, minKeyboardY == nil {
keyboardHeight = max(0.0, keyboardHeight - windowedHeightDifference)
}
}
if strongSelf.hostView.containerView is ChildWindowHostView, !isTablet {
keyboardHeight += 27.0
}
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
if duration > Double.ulpOfOne {
duration = 0.5
}
let curve: UInt = (notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7
let transitionCurve: ContainedViewLayoutTransitionCurve
if curve == 7 {
transitionCurve = .spring
} else {
transitionCurve = .easeInOut
}
var transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: transitionCurve)
if strongSelf.shouldNotAnimateLikelyKeyboardAutocorrectionSwitch, let inputHeight = strongSelf.windowLayout.inputHeight {
if abs(inputHeight - keyboardHeight) <= 44.1 {
transition = .immediate
}
}
strongSelf.updateLayout { $0.update(inputHeight: keyboardHeight.isLessThanOrEqualTo(0.0) ? nil : keyboardHeight, transition: transition, overrideTransition: false) }
}
})
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.keyboardTypeChangeObserver = NotificationCenter.default.addObserver(forName: UITextInputMode.currentInputModeDidChangeNotification, object: nil, queue: OperationQueue.main, using: { [weak self] notification in
if let strongSelf = self, let initialInputHeight = strongSelf.windowLayout.inputHeight, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.eventView).0 {
if firstResponder.textInputMode?.primaryLanguage != nil {
return
}
strongSelf.keyboardTypeChangeTimer?.invalidate()
let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: false, completion: {
if let strongSelf = self, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.eventView).0 {
if firstResponder.textInputMode?.primaryLanguage != nil {
return
}
if let keyboardManager = strongSelf.keyboardManager {
var updatedKeyboardHeight = keyboardManager.getCurrentKeyboardHeight()
if strongSelf.deviceMetrics.type == .tablet, abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 {
updatedKeyboardHeight = max(0.0, updatedKeyboardHeight - 24.0)
}
if !updatedKeyboardHeight.isEqual(to: initialInputHeight) {
strongSelf.updateLayout({ $0.update(inputHeight: updatedKeyboardHeight, transition: .immediate, overrideTransition: false) })
}
}
}
}, queue: Queue.mainQueue())
strongSelf.keyboardTypeChangeTimer = timer
timer.start()
}
})
}
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.updateLayout { $0.update(inVoiceOver: UIAccessibility.isVoiceOverRunning) }
}
})
}
let recognizer = WindowPanRecognizer(target: self, action: #selector(self.panGesture(_:)))
recognizer.cancelsTouchesInView = false
recognizer.delaysTouchesBegan = false
recognizer.delaysTouchesEnded = false
recognizer.delegate = self.keyboardGestureRecognizerDelegate
recognizer.isEnabled = self.deviceMetrics.type == .phone
recognizer.began = { [weak self] point in
self?.panGestureBegan(location: point)
}
recognizer.moved = { [weak self] point in
self?.panGestureMoved(location: point)
}
recognizer.ended = { [weak self] point, velocity in
self?.panGestureEnded(location: point, velocity: velocity)
}
self.windowPanRecognizer = recognizer
self.hostView.containerView.addGestureRecognizer(recognizer)
self.hostView.containerView.addSubview(self.badgeView)
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
if let statusBarChangeObserver = self.statusBarChangeObserver {
NotificationCenter.default.removeObserver(statusBarChangeObserver)
}
if let keyboardRotationChangeObserver = self.keyboardRotationChangeObserver {
NotificationCenter.default.removeObserver(keyboardRotationChangeObserver)
}
if let keyboardFrameChangeObserver = self.keyboardFrameChangeObserver {
NotificationCenter.default.removeObserver(keyboardFrameChangeObserver)
}
if let keyboardTypeChangeObserver = self.keyboardTypeChangeObserver {
NotificationCenter.default.removeObserver(keyboardTypeChangeObserver)
}
if let voiceOverStatusObserver = self.voiceOverStatusObserver {
NotificationCenter.default.removeObserver(voiceOverStatusObserver)
}
}
private var forceBadgeHidden = true
public func setForceBadgeHidden(_ hidden: Bool) {
guard hidden != self.forceBadgeHidden else {
return
}
self.forceBadgeHidden = hidden
self.updateBadgeVisibility()
}
private var proximityDimController: CustomDimController?
public func setProximityDimHidden(_ hidden: Bool) {
if !hidden {
if self.proximityDimController == nil {
let proximityDimController = CustomDimController(navigationBarPresentationData: nil)
self.proximityDimController = proximityDimController
(self.viewController as? NavigationController)?.presentOverlay(controller: proximityDimController, inGlobal: true, blockInteraction: false)
}
} else if let proximityDimController = self.proximityDimController {
self.proximityDimController = nil
proximityDimController.dismiss()
}
}
private func updateBadgeVisibility() {
let badgeIsHidden = !self.deviceMetrics.showAppBadge || self.forceBadgeHidden || self.windowLayout.size.width > self.windowLayout.size.height
if badgeIsHidden != self.badgeView.isHidden && !badgeIsHidden {
Queue.mainQueue().after(0.4) {
let badgeShouldBeHidden = !self.deviceMetrics.showAppBadge || self.forceBadgeHidden || self.windowLayout.size.width > self.windowLayout.size.height
if badgeShouldBeHidden == badgeIsHidden {
self.badgeView.isHidden = badgeIsHidden
}
}
} else {
self.badgeView.isHidden = badgeIsHidden
}
}
public func setForceInCallStatusBar(_ forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) {
if self.forceInCallStatusBarText != forceInCallStatusBarText {
self.forceInCallStatusBarText = forceInCallStatusBarText
self.updateLayout { $0.update(forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) }
self.invalidateTracingStatusBars()
}
}
private func invalidateTracingStatusBars() {
self.tracingStatusBarsInvalidated = true
self.hostView.eventView.setNeedsLayout()
}
public func invalidateDeferScreenEdgeGestures() {
self.shouldUpdateDeferScreenEdgeGestures = true
self.hostView.eventView.setNeedsLayout()
}
public func invalidatePrefersOnScreenNavigationHidden() {
self.shouldInvalidatePrefersOnScreenNavigationHidden = true
self.hostView.eventView.setNeedsLayout()
}
public func invalidateSupportedOrientations() {
self.shouldInvalidateSupportedOrientations = true
self.hostView.eventView.setNeedsLayout()
}
public func cancelInteractiveKeyboardGestures() {
self.windowPanRecognizer?.isEnabled = false
self.windowPanRecognizer?.isEnabled = true
if self.windowLayout.upperKeyboardInputPositionBound != nil {
self.updateLayout {
$0.update(upperKeyboardInputPositionBound: nil, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false)
}
}
if self.keyboardGestureBeginLocation != nil {
self.keyboardGestureBeginLocation = nil
}
}
public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.isInteractionBlocked {
return nil
}
if let result = self.topPresentationContext.hitTest(view: self.hostView.containerView, point: point, with: event) {
return result
}
if let coveringView = self.coveringView, !coveringView.isHidden, coveringView.superview != nil, coveringView.frame.contains(point) {
return coveringView.hitTest(point, with: event)
}
for view in self.hostView.eventView.subviews.reversed() {
let classString = NSStringFromClass(type(of: view))
if classString == "UITransitionView" || classString.contains("ContextMenuContainerView") {
if let result = view.hitTest(point, with: event) {
return result
}
}
}
if let result = self.overlayPresentationContext.hitTest(point, with: event) {
return result
}
for controller in self._topLevelOverlayControllers.reversed() {
if let result = controller.view.hitTest(point, with: event) {
return result
}
}
if let result = self.presentationContext.hitTest(view: self.hostView.containerView, point: point, with: event) {
return result
}
return self.viewController?.view.hitTest(point, with: event)
}
func updateSize(_ value: CGSize, duration: Double, orientation: UIInterfaceOrientation) {
let transition: ContainedViewLayoutTransition
if !duration.isZero {
transition = .animated(duration: duration, curve: .easeInOut)
} else {
transition = .immediate
}
self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(size: value, orientation: orientation), safeInsets: self.deviceMetrics.safeInsets(inLandscape: value.width > value.height), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) }
if let statusBarHost = self.statusBarHost, !statusBarHost.isApplicationInForeground {
self.layoutSubviews(force: true)
}
}
private var _rootController: ContainableController?
public var viewController: ContainableController? {
get {
return _rootController
}
set(value) {
if let rootController = self._rootController {
rootController.view.removeFromSuperview()
}
self._rootController = value
if let rootController = self._rootController {
if let rootController = rootController as? NavigationController {
rootController.statusBarHost = self.statusBarHost
rootController.updateSupportedOrientations = { [weak self] in
guard let strongSelf = self else {
return
}
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
let orientationToLock: UIInterfaceOrientationMask
if strongSelf.windowLayout.size.width < strongSelf.windowLayout.size.height {
orientationToLock = .portrait
} else {
orientationToLock = .landscape
}
if let _rootController = strongSelf._rootController {
supportedOrientations = supportedOrientations.intersection(_rootController.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
}
supportedOrientations = supportedOrientations.intersection(strongSelf.presentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
supportedOrientations = supportedOrientations.intersection(strongSelf.overlayPresentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
var resolvedOrientations: UIInterfaceOrientationMask
switch strongSelf.windowLayout.metrics.widthClass {
case .regular:
resolvedOrientations = supportedOrientations.regularSize
case .compact:
resolvedOrientations = supportedOrientations.compactSize
}
if resolvedOrientations.isEmpty {
resolvedOrientations = [.portrait]
}
strongSelf.hostView.updateSupportedInterfaceOrientations(resolvedOrientations)
}
rootController.keyboardViewManager = self.keyboardViewManager
rootController.inCallNavigate = { [weak self] in
self?.inCallNavigate?()
}
}
self.hostView.containerView.insertSubview(rootController.view, at: 0)
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero {
rootController.displayNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate)
}
}
self.hostView.eventView.setNeedsLayout()
}
}
private var _topLevelOverlayControllers: [ContainableController] = []
public var topLevelOverlayControllers: [ContainableController] {
get {
return _topLevelOverlayControllers
}
set(value) {
for controller in self._topLevelOverlayControllers {
if let controller = controller as? ViewController {
controller.statusBar.alphaUpdated = nil
}
controller.view.removeFromSuperview()
}
self._topLevelOverlayControllers = value
let layout = containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics)
for controller in self._topLevelOverlayControllers {
controller.displayNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
controller.containerLayoutUpdated(layout, transition: .immediate)
if let coveringView = self.coveringView {
self.hostView.containerView.insertSubview(controller.view, belowSubview: coveringView)
} else {
self.hostView.containerView.insertSubview(controller.view, belowSubview: self.badgeView)
}
if let controller = controller as? ViewController {
controller.statusBar.alphaUpdated = { [weak self] transition in
guard let strongSelf = self, let navigationController = strongSelf._rootController as? NavigationController else {
return
}
var isStatusBarHidden: Bool = false
for controller in strongSelf._topLevelOverlayControllers {
if let controller = controller as? ViewController {
if case .Hide = controller.statusBar.statusBarStyle {
isStatusBarHidden = true
}
}
}
navigationController.updateExternalStatusBarHidden(isStatusBarHidden, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}
}
}
public var coveringView: WindowCoveringView? {
didSet {
if self.coveringView !== oldValue {
if let oldValue = oldValue {
oldValue.layer.allowsGroupOpacity = true
oldValue.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak oldValue] _ in
oldValue?.removeFromSuperview()
})
}
if let coveringView = self.coveringView {
coveringView.layer.removeAnimation(forKey: "opacity")
coveringView.layer.allowsGroupOpacity = false
coveringView.alpha = 1.0
if let controller = self.topPresentationContext.controllers.first {
self.hostView.containerView.insertSubview(coveringView, belowSubview: controller.0.displayNode.view)
} else {
self.hostView.containerView.insertSubview(coveringView, belowSubview: self.badgeView)
}
if !self.windowLayout.size.width.isZero {
coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
coveringView.updateLayout(self.windowLayout.size)
}
}
}
}
}
private func layoutSubviews(force: Bool) {
if self.tracingStatusBarsInvalidated, let _ = keyboardManager {
self.tracingStatusBarsInvalidated = false
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
let orientationToLock: UIInterfaceOrientationMask
if self.windowLayout.size.width < self.windowLayout.size.height {
orientationToLock = .portrait
} else {
orientationToLock = .landscape
}
if let _rootController = self._rootController {
supportedOrientations = supportedOrientations.intersection(_rootController.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
}
supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
var resolvedOrientations: UIInterfaceOrientationMask
switch self.windowLayout.metrics.widthClass {
case .regular:
resolvedOrientations = supportedOrientations.regularSize
case .compact:
resolvedOrientations = supportedOrientations.compactSize
}
if resolvedOrientations.isEmpty {
resolvedOrientations = [.portrait]
}
self.hostView.updateSupportedInterfaceOrientations(resolvedOrientations)
self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures())
self.hostView.updatePrefersOnScreenNavigationHidden(self.collectPrefersOnScreenNavigationHidden())
self.shouldUpdateDeferScreenEdgeGestures = false
self.shouldInvalidatePrefersOnScreenNavigationHidden = false
self.shouldInvalidateSupportedOrientations = false
} else if self.shouldUpdateDeferScreenEdgeGestures || self.shouldInvalidatePrefersOnScreenNavigationHidden || self.shouldInvalidateSupportedOrientations {
self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures())
self.hostView.updatePrefersOnScreenNavigationHidden(self.collectPrefersOnScreenNavigationHidden())
self.shouldUpdateDeferScreenEdgeGestures = false
self.shouldInvalidatePrefersOnScreenNavigationHidden = false
if self.shouldInvalidateSupportedOrientations {
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
let orientationToLock: UIInterfaceOrientationMask
if self.windowLayout.size.width < self.windowLayout.size.height {
orientationToLock = .portrait
} else {
orientationToLock = .landscape
}
if let _rootController = self._rootController {
supportedOrientations = supportedOrientations.intersection(_rootController.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
}
supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
var resolvedOrientations: UIInterfaceOrientationMask
switch self.windowLayout.metrics.widthClass {
case .regular:
resolvedOrientations = supportedOrientations.regularSize
case .compact:
resolvedOrientations = supportedOrientations.compactSize
}
if resolvedOrientations.isEmpty {
resolvedOrientations = [.portrait]
}
self.hostView.updateSupportedInterfaceOrientations(resolvedOrientations)
self.shouldInvalidateSupportedOrientations = false
}
}
if force {
self.commitUpdatingLayout()
} else if !UIWindow.isDeviceRotating() {
if !self.hostView.isUpdatingOrientationLayout {
self.commitUpdatingLayout()
} else {
self.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in
if let strongSelf = self {
strongSelf.hostView.eventView.setNeedsLayout()
}
})
}
} else {
UIWindow.addPostDeviceOrientationDidChange({ [weak self] in
if let strongSelf = self {
strongSelf.hostView.eventView.setNeedsLayout()
}
})
}
}
var postUpdateToInterfaceOrientationBlocks: [() -> Void] = []
private func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
let blocks = self.postUpdateToInterfaceOrientationBlocks
self.postUpdateToInterfaceOrientationBlocks = []
for f in blocks {
f()
}
self._rootController?.updateToInterfaceOrientation(orientation)
self.presentationContext.updateToInterfaceOrientation(orientation)
self.overlayPresentationContext.updateToInterfaceOrientation(orientation)
self.topPresentationContext.updateToInterfaceOrientation(orientation)
for controller in self.topLevelOverlayControllers {
controller.updateToInterfaceOrientation(orientation)
}
}
public func addPostUpdateToInterfaceOrientationBlock(f: @escaping () -> Void) {
postUpdateToInterfaceOrientationBlocks.append(f)
}
private func updateLayout(_ update: (inout UpdatingLayout) -> ()) {
if self.updatingLayout == nil {
var updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate)
update(&updatingLayout)
if updatingLayout.layout != self.windowLayout {
self.updatingLayout = updatingLayout
self.hostView.eventView.setNeedsLayout()
}
} else {
update(&self.updatingLayout!)
self.hostView.eventView.setNeedsLayout()
}
}
private var isFirstLayout = true
private func commitUpdatingLayout() {
if let updatingLayout = self.updatingLayout {
self.updatingLayout = nil
if updatingLayout.layout != self.windowLayout || self.isFirstLayout {
self.isFirstLayout = false
let boundsSize = updatingLayout.layout.size
let isLandscape = boundsSize.width > boundsSize.height
var statusBarHeight: CGFloat? = self.deviceMetrics.statusBarHeight(for: boundsSize)
if let statusBarHeightValue = statusBarHeight, let statusBarHost = self.statusBarHost {
statusBarHeight = max(statusBarHeightValue, statusBarHost.statusBarFrame.size.height)
} else {
statusBarHeight = nil
}
if self.deviceMetrics.type == .tablet, let onScreenNavigationHeight = self.hostView.onScreenNavigationHeight, onScreenNavigationHeight != self.deviceMetrics.onScreenNavigationHeight(inLandscape: false, systemOnScreenNavigationHeight: self.hostView.onScreenNavigationHeight) {
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, scale: UIScreen.main.scale, statusBarHeight: statusBarHeight ?? 0.0, onScreenNavigationHeight: onScreenNavigationHeight)
}
let statusBarWasHidden = self.statusBarHidden
if statusBarHiddenInLandscape && isLandscape {
statusBarHeight = nil
self.statusBarHidden = true
} else {
self.statusBarHidden = false
}
if self.statusBarHidden != statusBarWasHidden {
self.tracingStatusBarsInvalidated = true
self.hostView.eventView.setNeedsLayout()
}
let previousInputOffset = inputHeightOffsetForLayout(self.windowLayout)
self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(size: updatingLayout.layout.size, orientation: updatingLayout.layout.metrics.orientation), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, safeInsets: updatingLayout.layout.safeInsets, onScreenNavigationHeight: self.deviceMetrics.onScreenNavigationHeight(inLandscape: isLandscape, systemOnScreenNavigationHeight: self.hostView.onScreenNavigationHeight), upperKeyboardInputPositionBound: updatingLayout.layout.upperKeyboardInputPositionBound, inVoiceOver: updatingLayout.layout.inVoiceOver)
let childLayout = containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics)
let childLayoutUpdated = self.updatedContainerLayout != childLayout
self.updatedContainerLayout = childLayout
if childLayoutUpdated {
var rootLayout = childLayout
let rootTransition = updatingLayout.transition
if self.presentationContext.isCurrentlyOpaque {
rootLayout.inputHeight = nil
}
if let rootController = self._rootController {
rootTransition.updateFrame(node: rootController.displayNode, frame: CGRect(origin: CGPoint(), size: self.windowLayout.size))
rootController.containerLayoutUpdated(rootLayout, transition: rootTransition)
}
self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
self.topPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
for controller in self.topLevelOverlayControllers {
updatingLayout.transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(), size: self.windowLayout.size))
controller.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
}
}
let updatedInputOffset = inputHeightOffsetForLayout(self.windowLayout)
if !previousInputOffset.isEqual(to: updatedInputOffset) {
let hide = updatingLayout.transition.isAnimated && updatingLayout.layout.upperKeyboardInputPositionBound == updatingLayout.layout.size.height
self.keyboardManager?.updateInteractiveInputOffset(updatedInputOffset, transition: updatingLayout.transition, completion: { [weak self] in
if let strongSelf = self, hide {
strongSelf.updateLayout {
$0.update(upperKeyboardInputPositionBound: nil, transition: .immediate, overrideTransition: false)
}
strongSelf.hostView.eventView.endEditing(true)
}
})
}
if let coveringView = self.coveringView {
coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
coveringView.updateLayout(self.windowLayout.size)
}
if let image = self.badgeView.image {
self.updateBadgeVisibility()
// MARK: Swiftgram
var badgeOffset: CGFloat
if case self.deviceMetrics = DeviceMetrics.iPhone14ProZoomed {
badgeOffset = self.deviceMetrics.statusBarHeight - DeviceMetrics.iPhone14ProZoomed.statusBarHeight
if self.deviceMetrics.modelHasDynamicIsland {
badgeOffset += 3.0
}
} else if case self.deviceMetrics = DeviceMetrics.iPhone14ProMaxZoomed {
badgeOffset = self.deviceMetrics.statusBarHeight - DeviceMetrics.iPhone14ProMaxZoomed.statusBarHeight
if self.deviceMetrics.modelHasDynamicIsland {
badgeOffset += 3.0
}
} else {
badgeOffset = self.deviceMetrics.statusBarHeight - DeviceMetrics.iPhone13ProMax.statusBarHeight
}
if badgeOffset != 0 {
badgeOffset += 3.0 // Centering badge in status bar for Dynamic island devices
}
self.badgeView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.windowLayout.size.width - image.size.width) / 2.0), y: 5.0 + badgeOffset), size: image.size)
}
}
}
}
public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) {
if level.rawValue <= 3, let controller = controller as? ViewController {
for presentedController in self.presentationContext.controllers.reversed() {
if let navigationController = presentedController.0 as? NavigationController {
navigationController.presentOverlay(controller: controller, inGlobal: false, blockInteraction: blockInteraction)
return
}
}
if let navigationController = self._rootController as? NavigationController {
navigationController.presentOverlay(controller: controller, inGlobal: false, blockInteraction: blockInteraction)
} else {
self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
}
} else {
if let controller = controller as? ViewController, controller.presentedOverCoveringView {
self.topPresentationContext.present(controller, on: level, completion: completion)
} else {
self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
}
}
}
public func presentInGlobalOverlay(_ controller: ContainableController) {
if let controller = controller as? ViewController {
if let navigationController = self._rootController as? NavigationController {
navigationController.presentOverlay(controller: controller, inGlobal: true)
return
}
}
self.overlayPresentationContext.present(controller)
}
public func addGlobalPortalHostView(sourceView: PortalSourceView) {
self.overlayPresentationContext.addGlobalPortalHostView(sourceView: sourceView)
}
public func presentNative(_ controller: UIViewController) {
if let nativeController = self.hostView.nativeController?() {
nativeController.present(controller, animated: true, completion: nil)
}
}
private func panGestureBegan(location: CGPoint) {
if self.windowLayout.upperKeyboardInputPositionBound != nil {
return
}
let keyboardGestureBeginLocation = location
let view = self.hostView.containerView
let (firstResponder, accessoryHeight) = getFirstResponderAndAccessoryHeight(view)
if let inputHeight = self.windowLayout.inputHeight, !inputHeight.isZero, keyboardGestureBeginLocation.y < self.windowLayout.size.height - inputHeight - (accessoryHeight ?? 0.0) {
var enableGesture = true
if let view = self.hostView.containerView.hitTest(location, with: nil) {
if doesViewTreeDisableInteractiveTransitionGestureRecognizer(view, keyboardOnly: true) {
enableGesture = false
}
}
if enableGesture, let _ = firstResponder {
self.keyboardGestureBeginLocation = keyboardGestureBeginLocation
self.keyboardGestureAccessoryHeight = accessoryHeight
}
}
}
private func panGestureMoved(location: CGPoint) {
if let keyboardGestureBeginLocation = self.keyboardGestureBeginLocation {
let currentLocation = location
let deltaY = keyboardGestureBeginLocation.y - location.y
if deltaY * deltaY >= 3.0 * 3.0 || self.windowLayout.upperKeyboardInputPositionBound != nil {
self.updateLayout {
$0.update(upperKeyboardInputPositionBound: currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0), transition: .immediate, overrideTransition: false)
}
}
}
}
public func simulateKeyboardDismiss(transition: ContainedViewLayoutTransition) {
var simulate = false
for controller in self.overlayPresentationContext.controllers {
if controller.isViewLoaded {
if controller.view.window != self.hostView.containerView.window {
simulate = true
break
}
}
}
if simulate {
self.updateLayout {
$0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: transition, overrideTransition: false)
}
} else {
self.hostView.containerView.endEditing(true)
}
}
private func panGestureEnded(location: CGPoint, velocity: CGPoint?) {
if self.keyboardGestureBeginLocation == nil {
return
}
self.keyboardGestureBeginLocation = nil
let currentLocation = location
let accessoryHeight = (self.keyboardGestureAccessoryHeight ?? 0.0)
var canDismiss = false
if let upperKeyboardInputPositionBound = self.windowLayout.upperKeyboardInputPositionBound, upperKeyboardInputPositionBound >= self.windowLayout.size.height - accessoryHeight {
canDismiss = true
} else if let velocity = velocity, velocity.y > 100.0 {
canDismiss = true
}
if canDismiss, let inputHeight = self.windowLayout.inputHeight, currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight {
self.updateLayout {
$0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false)
}
} else {
self.updateLayout {
$0.update(upperKeyboardInputPositionBound: nil, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false)
}
}
}
@objc func panGesture(_ recognizer: WindowPanRecognizer) {
switch recognizer.state {
case .began:
self.panGestureBegan(location: recognizer.location(in: recognizer.view))
case .changed:
self.panGestureMoved(location: recognizer.location(in: recognizer.view))
case .ended:
self.panGestureEnded(location: recognizer.location(in: recognizer.view), velocity: recognizer.velocity(in: recognizer.view))
case .cancelled:
self.panGestureEnded(location: recognizer.location(in: recognizer.view), velocity: nil)
default:
break
}
}
private func collectScreenEdgeGestures() -> UIRectEdge {
var edges: UIRectEdge = []
if let navigationController = self._rootController as? NavigationController, let overlayController = navigationController.topOverlayController {
edges = edges.union(overlayController.deferScreenEdgeGestures)
}
edges = edges.union(self.presentationContext.combinedDeferScreenEdgeGestures())
for controller in self.topLevelOverlayControllers {
if let controller = controller as? ViewController {
edges = edges.union(controller.deferScreenEdgeGestures)
}
}
return edges
}
private func collectPrefersOnScreenNavigationHidden() -> Bool {
var hidden = false
if let navigationController = self._rootController as? NavigationController, let overlayController = navigationController.topOverlayController {
hidden = hidden || overlayController.prefersOnScreenNavigationHidden
}
hidden = hidden || self.presentationContext.combinedPrefersOnScreenNavigationHidden()
for controller in self.topLevelOverlayControllers {
if let controller = controller as? ViewController {
hidden = hidden || controller.prefersOnScreenNavigationHidden
}
}
return hidden
}
public func forEachViewController(_ f: (ContainableController) -> Bool, excludeNavigationSubControllers: Bool = false) {
if let navigationController = self._rootController as? NavigationController {
if !excludeNavigationSubControllers {
for case let controller as ContainableController in navigationController.viewControllers {
let _ = f(controller)
}
}
if let controller = navigationController.topOverlayController {
let _ = f(controller)
}
}
for (controller, _) in self.presentationContext.controllers {
if !f(controller) {
break
}
}
for controller in self.topLevelOverlayControllers {
if !f(controller) {
break
}
}
for (controller, _) in self.topPresentationContext.controllers {
if !f(controller) {
break
}
}
}
public func doNotAnimateLikelyKeyboardAutocorrectionSwitch() {
self.shouldNotAnimateLikelyKeyboardAutocorrectionSwitch = true
DispatchQueue.main.async {
self.shouldNotAnimateLikelyKeyboardAutocorrectionSwitch = false
}
}
}
private class CustomDimController: ViewController {
class Node: ASDisplayNode {
override init() {
super.init()
self.backgroundColor = .black
}
}
override init(navigationBarPresentationData: NavigationBarPresentationData?) {
super.init(navigationBarPresentationData: nil)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadDisplayNode() {
let node = Node()
self.displayNode = node
}
}