import Foundation import UIKit import ComponentFlow import Display import SwiftSignalKit import TelegramPresentationData import AccountContext import ComponentDisplayAdapters open class ViewControllerComponentContainer: ViewController { public enum NavigationBarAppearance { case none case transparent case `default` } public enum StatusBarStyle { case none case ignore case `default` } public final class Environment: Equatable { public let statusBarHeight: CGFloat public let navigationHeight: CGFloat public let safeInsets: UIEdgeInsets public let inputHeight: CGFloat public let metrics: LayoutMetrics public let deviceMetrics: DeviceMetrics public let orientation: UIInterfaceOrientation? public let isVisible: Bool public let theme: PresentationTheme public let strings: PresentationStrings public let dateTimeFormat: PresentationDateTimeFormat public let controller: () -> ViewController? public init( statusBarHeight: CGFloat, navigationHeight: CGFloat, safeInsets: UIEdgeInsets, inputHeight: CGFloat, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, orientation: UIInterfaceOrientation? = nil, isVisible: Bool, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, controller: @escaping () -> ViewController? ) { self.statusBarHeight = statusBarHeight self.navigationHeight = navigationHeight self.safeInsets = safeInsets self.inputHeight = inputHeight self.metrics = metrics self.deviceMetrics = deviceMetrics self.orientation = orientation self.isVisible = isVisible self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat self.controller = controller } public static func ==(lhs: Environment, rhs: Environment) -> Bool { if lhs === rhs { return true } if lhs.statusBarHeight != rhs.statusBarHeight { return false } if lhs.navigationHeight != rhs.navigationHeight { return false } if lhs.safeInsets != rhs.safeInsets { return false } if lhs.inputHeight != rhs.inputHeight { return false } if lhs.metrics != rhs.metrics { return false } if lhs.deviceMetrics != rhs.deviceMetrics { return false } if lhs.orientation != rhs.orientation { return false } if lhs.isVisible != rhs.isVisible { return false } if lhs.theme !== rhs.theme { return false } if lhs.strings !== rhs.strings { return false } if lhs.dateTimeFormat != rhs.dateTimeFormat { return false } return true } } public final class AnimateInTransition { } public final class AnimateOutTransition { } public final class Node: ViewControllerTracingNode { fileprivate var presentationData: PresentationData private weak var controller: ViewControllerComponentContainer? private var component: AnyComponent var theme: PresentationTheme? public let hostView: ComponentHostView private var currentIsVisible: Bool = false private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? init(context: AccountContext, controller: ViewControllerComponentContainer, component: AnyComponent, theme: PresentationTheme?) { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.controller = controller self.component = component self.theme = theme self.hostView = ComponentHostView() super.init() self.view.addSubview(self.hostView) } func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) { self.currentLayout = (layout, navigationHeight) var theme = self.theme ?? self.presentationData.theme if theme.list.blocksBackgroundColor.rgb == theme.list.plainBackgroundColor.rgb { theme = theme.withModalBlocksBackground() } let environment = ViewControllerComponentContainer.Environment( statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, isVisible: self.currentIsVisible, theme: theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, controller: { [weak self] in return self?.controller } ) let _ = self.hostView.update( transition: transition, component: self.component, environment: { environment }, containerSize: layout.size ) transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil) } func updateIsVisible(isVisible: Bool, animated: Bool) { if self.currentIsVisible == isVisible { return } self.currentIsVisible = isVisible guard let currentLayout = self.currentLayout else { return } self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: animated ? Transition(animation: .none).withUserData(isVisible ? AnimateInTransition() : AnimateOutTransition()) : .immediate) } func updateComponent(component: AnyComponent, transition: Transition) { self.component = component guard let currentLayout = self.currentLayout else { return } self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: transition) } } public var node: Node { return self.displayNode as! Node } private let context: AccountContext private var theme: PresentationTheme? private let component: AnyComponent private var presentationDataDisposable: Disposable? public private(set) var validLayout: ContainerViewLayout? public init(context: AccountContext, component: C, navigationBarAppearance: NavigationBarAppearance, statusBarStyle: StatusBarStyle = .default, theme: PresentationTheme? = nil) where C.EnvironmentType == ViewControllerComponentContainer.Environment { self.context = context self.component = AnyComponent(component) self.theme = theme let presentationData = context.sharedContext.currentPresentationData.with { $0 } let navigationBarPresentationData: NavigationBarPresentationData? switch navigationBarAppearance { case .none: navigationBarPresentationData = nil case .transparent: navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true) case .default: navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData) } super.init(navigationBarPresentationData: navigationBarPresentationData) self.presentationDataDisposable = (self.context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { strongSelf.node.presentationData = presentationData switch statusBarStyle { case .none: strongSelf.statusBar.statusBarStyle = .Hide case .ignore: strongSelf.statusBar.statusBarStyle = .Ignore case .default: strongSelf.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style } if let layout = strongSelf.validLayout { strongSelf.containerLayoutUpdated(layout, transition: .immediate) } } }) switch statusBarStyle { case .none: self.statusBar.statusBarStyle = .Hide case .ignore: self.statusBar.statusBarStyle = .Ignore case .default: self.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style } } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { self.presentationDataDisposable?.dispose() } override open func loadDisplayNode() { self.displayNode = Node(context: self.context, controller: self, component: self.component, theme: self.theme) self.displayNodeDidLoad() } override open func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.node.updateIsVisible(isVisible: true, animated: true) } override open func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.node.updateIsVisible(isVisible: false, animated: animated) } open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag, completion: completion) } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) let navigationHeight = self.navigationLayout(layout: layout).navigationFrame.maxY self.validLayout = layout self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) } public func updateComponent(component: AnyComponent, transition: Transition) { self.node.updateComponent(component: component, transition: transition) } }