From 538e3dbe70de40ecd6f977695c253dc43dce73b3 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 14 Sep 2019 00:13:50 +0400 Subject: [PATCH] Upgraded navigation --- .../Sources/AccountContext.swift | 2 +- .../Sources/CallListController.swift | 1 + .../ChatListUI/Sources/ChatContextMenus.swift | 12 +- .../Sources/ChatListController.swift | 3 +- .../Sources/CreatePollController.swift | 1 + .../Sources/ContactsController.swift | 4 +- .../Sources/InviteContactsController.swift | 2 + .../ContainedViewLayoutTransition.swift | 8 +- submodules/Display/Display/GridNode.swift | 2 +- .../Navigation/NavigationContainer.swift | 377 ++++ .../Navigation/NavigationController.swift | 793 ++++++++ .../Display/Navigation/NavigationLayout.swift | 76 + .../Navigation/NavigationModalContainer.swift | 196 ++ .../Navigation/NavigationModalFrame.swift | 113 ++ .../Navigation/NavigationSplitContainer.swift | 43 + .../Display/NavigationController.swift | 1689 ----------------- .../Display/ScrollToTopProxyView.swift | 2 + .../Display/Display/StatusBarHost.swift | 2 + .../Display/Display/StatusBarManager.swift | 4 +- .../Display/Display/ViewController.swift | 15 +- .../Display/Display/WindowContent.swift | 6 +- .../Sources/InstantPageControllerNode.swift | 44 +- .../Sources/ItemListController.swift | 8 - .../LegacyUI/Sources/LegacyController.swift | 2 +- .../Sources/LegacyLocationPicker.swift | 3 +- .../Sources/ChannelAdminController.swift | 1 + .../Sources/ChannelAdminsController.swift | 18 +- .../Sources/ChannelBlacklistController.swift | 4 +- .../Sources/ChannelMembersController.swift | 8 +- .../ChannelMembersSearchContainerNode.swift | 10 +- .../ChannelMembersSearchController.swift | 14 +- .../ChannelMembersSearchControllerNode.swift | 6 +- .../ChannelPermissionsController.swift | 4 +- .../Sources/DeviceContactInfoController.swift | 16 +- .../Sources/GroupInfoController.swift | 8 +- .../Sources/GroupInfoSearchItem.swift | 14 +- .../Sources/PhoneLabelController.swift | 1 + .../Sources/UserInfoController.swift | 10 +- .../ProxyListSettingsController.swift | 11 +- .../ProxyServerSettingsController.swift | 1 + .../SettingsUI/Sources/DebugController.swift | 8 +- .../Sources/EditSettingsController.swift | 2 +- .../NotificationExceptionControllerNode.swift | 2 +- .../BlockedPeersController.swift | 2 +- .../Themes/ThemePreviewController.swift | 9 +- .../Sources/UsernameSetupController.swift | 1 + .../Sources/ComponentsThemes.swift | 9 +- .../AuthorizationSequenceController.swift | 10 +- .../TelegramUI/ChatController.swift | 47 +- .../TelegramUI/ComposeController.swift | 6 +- .../ContactSelectionController.swift | 13 - .../TelegramUI/OpenChatMessage.swift | 15 +- .../TelegramUI/OpenResolvedUrl.swift | 7 +- .../PeerMediaCollectionController.swift | 17 +- .../TelegramUI/PeerSelectionController.swift | 10 +- .../TelegramUI/SharedAccountContext.swift | 8 +- .../Sources/WebSearchController.swift | 8 - 57 files changed, 1799 insertions(+), 1909 deletions(-) create mode 100644 submodules/Display/Display/Navigation/NavigationContainer.swift create mode 100644 submodules/Display/Display/Navigation/NavigationController.swift create mode 100644 submodules/Display/Display/Navigation/NavigationLayout.swift create mode 100644 submodules/Display/Display/Navigation/NavigationModalContainer.swift create mode 100644 submodules/Display/Display/Navigation/NavigationModalFrame.swift create mode 100644 submodules/Display/Display/Navigation/NavigationSplitContainer.swift delete mode 100644 submodules/Display/Display/NavigationController.swift diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 2a23a22483..8fa9a12675 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -432,7 +432,7 @@ public protocol SharedAccountContext: class { func resolveUrl(account: Account, url: String) -> Signal func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void) func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) - func openAddPersonContact(context: AccountContext, peerId: PeerId, present: @escaping (ViewController, Any?) -> Void) + func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) func navigateToCurrentCall() diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index b48ac95951..4e182ea7e4 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -194,6 +194,7 @@ public final class CallListController: ViewController { @objc func callPressed() { let controller = self.context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: self.context, title: { $0.Calls_NewCall })) + controller.navigationPresentation = .modal self.createActionDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller, weak self] peer in diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 6e6860de4f..bfc0bd3926 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -84,9 +84,13 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC if !isSavedMessages, let peer = peer as? TelegramUser { if !transaction.isPeerContact(peerId: peer.id) { items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToContacts, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { _, f in - context.sharedContext.openAddPersonContact(context: context, peerId: peerId, present: { controller, arguments in + context.sharedContext.openAddPersonContact(context: context, peerId: peerId, pushController: { controller in + if let navigationController = chatListController?.navigationController as? NavigationController { + navigationController.pushViewController(controller) + } + }, present: { c, a in if let chatListController = chatListController { - chatListController.present(controller, in: .window(.root), with: arguments) + chatListController.present(c, in: .window(.root), with: a) } }) f(.default) @@ -224,6 +228,10 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC }))) } + if let item = items.last, case .separator = item { + items.removeLast() + } + return items } } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 187037a529..e9806be990 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1020,8 +1020,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, @objc private func composePressed() { let controller = self.context.sharedContext.makeComposeController(context: self.context) - self.present(controller, in: .window(.root)) - //(self.navigationController as? NavigationController)?.replaceAllButRootController(self.context.sharedContext.makeComposeController(context: self.context), animated: true) + (self.navigationController as? NavigationController)?.pushViewController(controller) } public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index 72d7283927..90fcc1afc6 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -387,6 +387,7 @@ public func createPollController(context: AccountContext, peerId: PeerId, comple } let controller = ItemListController(context: context, state: signal) + controller.navigationPresentation = .modal presentControllerImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) } diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 1ef6b4a377..c622232279 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -523,7 +523,7 @@ public class ContactsController: ViewController { switch status { case .allowed: let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: []) - strongSelf.present(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in guard let strongSelf = self else { return } @@ -534,7 +534,7 @@ public class ContactsController: ViewController { } else { (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) } - }), completed: nil, cancelled: nil), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }), completed: nil, cancelled: nil)) case .notDetermined: DeviceAccess.authorizeAccess(to: .contacts) default: diff --git a/submodules/ContactListUI/Sources/InviteContactsController.swift b/submodules/ContactListUI/Sources/InviteContactsController.swift index 94735d84bf..bd64604591 100644 --- a/submodules/ContactListUI/Sources/InviteContactsController.swift +++ b/submodules/ContactListUI/Sources/InviteContactsController.swift @@ -38,6 +38,8 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationPresentation = .modal + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.title = self.presentationData.strings.Contacts_InviteFriends diff --git a/submodules/Display/Display/ContainedViewLayoutTransition.swift b/submodules/Display/Display/ContainedViewLayoutTransition.swift index 78812f3540..8d03e5ed9d 100644 --- a/submodules/Display/Display/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Display/ContainedViewLayoutTransition.swift @@ -575,6 +575,7 @@ public extension ContainedViewLayoutTransition { func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { if !node.isNodeLoaded { node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0) + completion?(true) return } let t = node.layer.sublayerTransform @@ -606,12 +607,13 @@ public extension ContainedViewLayoutTransition { func updateSublayerTransformScaleAndOffset(node: ASDisplayNode, scale: CGFloat, offset: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) { if !node.isNodeLoaded { node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0) + completion?(true) return } let t = node.layer.sublayerTransform let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) - let currentOffset = CGPoint(x: t.m41, y: t.m42) - if currentScale.isEqual(to: scale) && currentOffset == offset { + let currentOffset = CGPoint(x: t.m41 / currentScale, y: t.m42 / currentScale) + if abs(currentScale - scale) <= CGFloat.ulpOfOne && abs(currentOffset.x - offset.x) <= CGFloat.ulpOfOne && abs(currentOffset.y - offset.y) <= CGFloat.ulpOfOne { if let completion = completion { completion(true) } @@ -647,6 +649,7 @@ public extension ContainedViewLayoutTransition { func updateSublayerTransformScale(node: ASDisplayNode, scale: CGPoint, completion: ((Bool) -> Void)? = nil) { if !node.isNodeLoaded { node.subnodeTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + completion?(true) return } let t = node.layer.sublayerTransform @@ -713,6 +716,7 @@ public extension ContainedViewLayoutTransition { func updateTransformScale(node: ASDisplayNode, scale: CGPoint, completion: ((Bool) -> Void)? = nil) { if !node.isNodeLoaded { node.subnodeTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + completion?(true) return } let t = node.layer.transform diff --git a/submodules/Display/Display/GridNode.swift b/submodules/Display/Display/GridNode.swift index 57ae8c020c..304b3c6f30 100644 --- a/submodules/Display/Display/GridNode.swift +++ b/submodules/Display/Display/GridNode.swift @@ -759,7 +759,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func lowestSectionNode() -> ASDisplayNode? { + public func lowestSectionNode() -> ASDisplayNode? { var lowestHeaderNode: ASDisplayNode? var lowestHeaderNodeIndex: Int? for (_, headerNode) in self.sectionNodes { diff --git a/submodules/Display/Display/Navigation/NavigationContainer.swift b/submodules/Display/Display/Navigation/NavigationContainer.swift new file mode 100644 index 0000000000..d38f728e0f --- /dev/null +++ b/submodules/Display/Display/Navigation/NavigationContainer.swift @@ -0,0 +1,377 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit + +final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate { + private final class Child { + let value: ViewController + var layout: ContainerViewLayout + + init(value: ViewController, layout: ContainerViewLayout) { + self.value = value + self.layout = layout + } + } + + private final class PendingChild { + enum TransitionType { + case push + case pop + } + + let value: Child + let transitionType: TransitionType + let transition: ContainedViewLayoutTransition + let disposable: MetaDisposable = MetaDisposable() + var isReady: Bool = false + + init(value: Child, transitionType: TransitionType, transition: ContainedViewLayoutTransition, update: @escaping (PendingChild) -> Void) { + self.value = value + self.transitionType = transitionType + self.transition = transition + var localIsReady: Bool? + self.disposable.set((value.value.ready.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] _ in + if localIsReady == nil { + localIsReady = true + } else if let strongSelf = self { + update(strongSelf) + } + })) + if let localIsReady = localIsReady { + self.isReady = true + } else { + localIsReady = false + } + } + + deinit { + self.disposable.dispose() + } + } + + private final class TopTransition { + let previous: Child + let coordinator: NavigationTransitionCoordinator + + init(previous: Child, coordinator: NavigationTransitionCoordinator) { + self.previous = previous + self.coordinator = coordinator + } + } + + private struct State { + var layout: ContainerViewLayout? + var canBeClosed: Bool? + var top: Child? + var transition: TopTransition? + var pending: PendingChild? + } + + private(set) var controllers: [ViewController] = [] + private var state: State = State(layout: nil, canBeClosed: nil, top: nil, transition: nil, pending: nil) + + private(set) var isReady: Bool = false + var isReadyUpdated: (() -> Void)? + var controllerRemoved: (ViewController) -> Void + var keyboardManager: KeyboardManager? { + didSet { + if self.keyboardManager !== oldValue { + self.keyboardManager?.surfaces = self.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? [] + } + } + } + + init(controllerRemoved: @escaping (ViewController) -> Void) { + self.controllerRemoved = controllerRemoved + + super.init() + } + + override func didLoad() { + super.didLoad() + + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + panRecognizer.delegate = self + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = true + self.view.addGestureRecognizer(panRecognizer) + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { + return true + } + return false + } + + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + guard let layout = self.state.layout else { + return + } + guard self.state.transition == nil else { + return + } + let beginGesture = self.controllers.count > 1 + + if beginGesture { + let topController = self.controllers[self.controllers.count - 1] + let bottomController = self.controllers[self.controllers.count - 2] + + if let topController = topController as? ViewController { + if !topController.attemptNavigation({ [weak self] in + //let _ = self?.popViewController(animated: true) + }) { + return + } + } + + topController.viewWillDisappear(true) + let topView = topController.view! + bottomController.containerLayoutUpdated(layout, transition: .immediate) + bottomController.viewWillAppear(true) + let bottomView = bottomController.view! + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: topController.navigationBar, bottomView: bottomView, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: { [weak self] progress, transition in + if let strongSelf = self { + strongSelf.keyboardManager?.surfaces = strongSelf.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? [] + /*for i in 0 ..< strongSelf._viewControllers.count { + if let controller = strongSelf._viewControllers[i].controller as? ViewController { + if i < strongSelf._viewControllers.count - 1 { + controller.updateNavigationCustomData((strongSelf.viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0 - progress, transition: transition) + } else { + controller.updateNavigationCustomData(nil, progress: 1.0 - progress, transition: transition) + } + } + }*/ + } + }) + bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true) + self.state.transition = TopTransition(previous: Child(value: bottomController, layout: layout), coordinator: navigationTransitionCoordinator) + } + case .changed: + if let navigationTransitionCoordinator = self.state.transition?.coordinator, !navigationTransitionCoordinator.animatingCompletion { + let translation = recognizer.translation(in: self.view).x + let progress = max(0.0, min(1.0, translation / self.view.frame.width)) + navigationTransitionCoordinator.progress = progress + } + case .ended, .cancelled: + if let navigationTransitionCoordinator = self.state.transition?.coordinator, !navigationTransitionCoordinator.animatingCompletion { + let velocity = recognizer.velocity(in: self.view).x + + if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { + //(self.view as! NavigationControllerView).inTransition = true + navigationTransitionCoordinator.animateCompletion(velocity, completion: { [weak self] in + guard let strongSelf = self, let layout = strongSelf.state.layout, let transition = strongSelf.state.transition, let top = strongSelf.state.top else { + return + } + //(self.view as! NavigationControllerView).inTransition = false + + let topController = top.value + let bottomController = transition.previous.value + strongSelf.keyboardManager?.updateInteractiveInputOffset(layout.size.height, transition: .immediate, completion: {}) + topController.view.endEditing(true) + + strongSelf.state.transition = nil + + strongSelf.controllerRemoved(top.value) + + //topController.viewDidDisappear(true) + //bottomController.viewDidAppear(true) + }) + } else { + /*if self.viewControllers.count >= 2 { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewWillAppear(true) + bottomController.viewWillDisappear(true) + }*/ + + //(self.view as! NavigationControllerView).inTransition = true + navigationTransitionCoordinator.animateCancel({ [weak self] in + guard let strongSelf = self, let top = strongSelf.state.top, let transition = strongSelf.state.transition else { + return + } + //(self.view as! NavigationControllerView).inTransition = false + strongSelf.state.transition = nil + + top.value.viewDidAppear(true) + transition.previous.value.viewDidDisappear(true) + }) + } + } + default: + break + } + } + + func update(layout: ContainerViewLayout, canBeClosed: Bool, controllers: [ViewController], transition: ContainedViewLayoutTransition) { + let previousControllers = self.controllers + self.controllers = controllers + + for i in 0 ..< controllers.count { + if i == 0 { + if canBeClosed { + controllers[i].navigationBar?.previousItem = .close + } else { + controllers[i].navigationBar?.previousItem = nil + } + } else { + controllers[i].navigationBar?.previousItem = .item(controllers[i - 1].navigationItem) + } + } + + self.state.layout = layout + self.state.canBeClosed = canBeClosed + + if controllers.last !== self.state.top?.value { + if controllers.last !== self.state.pending?.value.value { + self.state.pending = nil + if let last = controllers.last { + let transitionType: PendingChild.TransitionType + if !previousControllers.contains(where: { $0 === last }) { + transitionType = .push + } else { + transitionType = .pop + } + self.state.pending = PendingChild(value: self.makeChild(layout: layout, value: last), transitionType: transitionType, transition: transition, update: { [weak self] pendingChild in + self?.pendingChildIsReady(pendingChild) + }) + } + } + } + + if let pending = self.state.pending { + if pending.isReady { + self.state.pending = nil + let previous = self.state.top + self.state.top = pending.value + self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: layout, transition: pending.transition) + if !self.isReady { + self.isReady = true + self.isReadyUpdated?() + } + } + } + + if controllers.isEmpty && self.state.top != nil { + let previous = self.state.top + self.state.top = nil + self.topTransition(from: previous, to: nil, transitionType: .pop, layout: layout, transition: .immediate) + } + + if let top = self.state.top { + self.applyLayout(layout: layout, to: top, transition: transition) + } + + if self.state.transition == nil { + self.keyboardManager?.surfaces = self.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? [] + } + } + + private func topTransition(from fromValue: Child?, to toValue: Child?, transitionType: PendingChild.TransitionType, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + if case .animated = transition, let fromValue = fromValue, let toValue = toValue { + self.keyboardManager?.surfaces = fromValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? [] + if let currentTransition = self.state.transition { + assertionFailure() + } + + fromValue.value.viewWillDisappear(true) + toValue.value.viewWillAppear(true) + toValue.value.setIgnoreAppearanceMethodInvocations(true) + if let layout = self.state.layout { + toValue.value.displayNode.frame = CGRect(origin: CGPoint(), size: layout.size) + } + let mappedTransitionType: NavigationTransition + let topController: ViewController + let bottomController: ViewController + switch transitionType { + case .push: + mappedTransitionType = .Push + self.addSubnode(toValue.value.displayNode) + topController = toValue.value + bottomController = fromValue.value + case .pop: + mappedTransitionType = .Pop + self.insertSubnode(toValue.value.displayNode, belowSubnode: fromValue.value.displayNode) + topController = fromValue.value + bottomController = toValue.value + } + toValue.value.setIgnoreAppearanceMethodInvocations(false) + + let topTransition = TopTransition(previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, container: self.view, topView: topController.view, topNavigationBar: topController.navigationBar, bottomView: bottomController.view, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: nil)) + self.state.transition = topTransition + + topTransition.coordinator.animateCompletion(0.0, completion: { [weak self, weak topTransition] in + guard let strongSelf = self, let topTransition = topTransition, strongSelf.state.transition === topTransition else { + return + } + strongSelf.state.transition = nil + + topTransition.previous.value.setIgnoreAppearanceMethodInvocations(true) + topTransition.previous.value.displayNode.removeFromSupernode() + topTransition.previous.value.setIgnoreAppearanceMethodInvocations(false) + topTransition.previous.value.viewDidDisappear(true) + if let toValue = strongSelf.state.top, let layout = strongSelf.state.layout { + toValue.value.displayNode.frame = CGRect(origin: CGPoint(), size: layout.size) + strongSelf.applyLayout(layout: layout, to: toValue, transition: .immediate) + toValue.value.viewDidAppear(true) + strongSelf.keyboardManager?.surfaces = toValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? [] + } + }) + } else { + self.keyboardManager?.surfaces = toValue?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? [] + if let fromValue = fromValue { + fromValue.value.viewWillDisappear(false) + fromValue.value.setIgnoreAppearanceMethodInvocations(true) + fromValue.value.displayNode.removeFromSupernode() + fromValue.value.setIgnoreAppearanceMethodInvocations(false) + fromValue.value.viewDidDisappear(false) + } + if let toValue = toValue { + self.applyLayout(layout: layout, to: toValue, transition: .immediate) + toValue.value.displayNode.frame = CGRect(origin: CGPoint(), size: layout.size) + toValue.value.viewWillAppear(false) + toValue.value.setIgnoreAppearanceMethodInvocations(true) + self.addSubnode(toValue.value.displayNode) + toValue.value.setIgnoreAppearanceMethodInvocations(false) + toValue.value.viewDidAppear(false) + } + } + } + + private func makeChild(layout: ContainerViewLayout, value: ViewController) -> Child { + value.containerLayoutUpdated(layout, transition: .immediate) + return Child(value: value, layout: layout) + } + + private func applyLayout(layout: ContainerViewLayout, to child: Child, transition: ContainedViewLayoutTransition) { + if child.layout != layout { + child.layout = layout + child.value.containerLayoutUpdated(layout, transition: transition) + } + } + + private func pendingChildIsReady(_ child: PendingChild) { + if let pending = self.state.pending, pending === child { + pending.isReady = true + self.performUpdate() + } + } + + private func performUpdate() { + if let layout = self.state.layout, let canBeClosed = self.state.canBeClosed { + self.update(layout: layout, canBeClosed: canBeClosed, controllers: self.controllers, transition: .immediate) + } + } +} diff --git a/submodules/Display/Display/Navigation/NavigationController.swift b/submodules/Display/Display/Navigation/NavigationController.swift new file mode 100644 index 0000000000..d3ad5cd60b --- /dev/null +++ b/submodules/Display/Display/Navigation/NavigationController.swift @@ -0,0 +1,793 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit + +public enum NavigationStatusBarStyle { + case black + case white +} + +public final class NavigationControllerTheme { + public let statusBar: NavigationStatusBarStyle + public let navigationBar: NavigationBarTheme + public let emptyAreaColor: UIColor + + public init(statusBar: NavigationStatusBarStyle, navigationBar: NavigationBarTheme, emptyAreaColor: UIColor) { + self.statusBar = statusBar + self.navigationBar = navigationBar + self.emptyAreaColor = emptyAreaColor + } +} + +public struct NavigationAnimationOptions : OptionSet { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public static let removeOnMasterDetails = NavigationAnimationOptions(rawValue: 1 << 0) +} + +private final class NavigationControllerContainerView: UIView { + override class var layerClass: AnyClass { + return CATracingLayer.self + } +} + +public enum NavigationEmptyDetailsBackgoundMode { + case image(UIImage) + case wallpaper(UIImage) +} + +private final class NavigationControllerView: UITracingLayerView { + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override class var layerClass: AnyClass { + return CATracingLayer.self + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } +} + +private enum ControllerTransition { + case none + case appearance +} + +private final class ControllerRecord { + let controller: UIViewController + var transition: ControllerTransition = .none + + init(controller: UIViewController) { + self.controller = controller + } +} + +public enum NavigationControllerMode { + case single + case automaticMasterDetail +} + +public enum MasterDetailLayoutBlackout : Equatable { + case master + case details +} + +private enum RootContainer { + case flat(NavigationContainer) + case split(NavigationSplitContainer) +} + +open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { + public var isOpaqueWhenInOverlay: Bool = true + public var blocksBackgroundWhenInOverlay: Bool = true + public var isModalWhenInOverlay: Bool = false + public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)? + + private let _ready = Promise(true) + open var ready: Promise { + return self._ready + } + + private var masterDetailsBlackout: MasterDetailLayoutBlackout? + private var backgroundDetailsMode: NavigationEmptyDetailsBackgoundMode? + + public var lockOrientation: Bool = false + + public var deferScreenEdgeGestures: UIRectEdge = UIRectEdge() + + private let mode: NavigationControllerMode + private var theme: NavigationControllerTheme + + public private(set) weak var overlayPresentingController: ViewController? + + private var controllerView: NavigationControllerView { + return self.view as! NavigationControllerView + } + + private var rootContainer: RootContainer? + private var rootModalFrame: NavigationModalFrame? + private var modalContainers: [NavigationModalContainer] = [] + private var validLayout: ContainerViewLayout? + private var validStatusBarStyle: NavigationStatusBarStyle? + + private var scheduledLayoutTransitionRequestId: Int = 0 + private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)? + + private var _presentedViewController: UIViewController? + open override var presentedViewController: UIViewController? { + return self._presentedViewController + } + + private var _viewControllers: [ViewController] = [] + override open var viewControllers: [UIViewController] { + get { + return self._viewControllers.map { $0 as! ViewController } + } set(value) { + self.setViewControllers(value, animated: false) + } + } + + override open var topViewController: UIViewController? { + return self._viewControllers.last + } + + private var _displayNode: ASDisplayNode? + public var displayNode: ASDisplayNode { + return self._displayNode! + } + + var statusBarHost: StatusBarHost? + var keyboardManager: KeyboardManager? + + public func updateMasterDetailsBlackout(_ blackout: MasterDetailLayoutBlackout?, transition: ContainedViewLayoutTransition) { + self.masterDetailsBlackout = blackout + if isViewLoaded { + self.view.endEditing(true) + } + self.requestLayout(transition: transition) + } + + public func updateBackgroundDetailsMode(_ mode: NavigationEmptyDetailsBackgoundMode?, transition: ContainedViewLayoutTransition) { + self.backgroundDetailsMode = mode + self.requestLayout(transition: transition) + } + + public init(mode: NavigationControllerMode, theme: NavigationControllerTheme, backgroundDetailsMode: NavigationEmptyDetailsBackgoundMode? = nil) { + self.mode = mode + self.theme = theme + self.backgroundDetailsMode = backgroundDetailsMode + + super.init(nibName: nil, bundle: nil) + } + + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + preconditionFailure() + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { + var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) + if let controller = self.viewControllers.last { + if let controller = controller as? ViewController { + if controller.lockOrientation { + if let lockedOrientation = controller.lockedOrientation { + supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: lockedOrientation, compactSize: lockedOrientation)) + } else { + supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: currentOrientationToLock, compactSize: currentOrientationToLock)) + } + } else { + supportedOrientations = supportedOrientations.intersection(controller.supportedOrientations) + } + } + } + return supportedOrientations + } + + public func updateTheme(_ theme: NavigationControllerTheme) { + let statusBarStyleUpdated = self.theme.statusBar != theme.statusBar + self.theme = theme + if self.isViewLoaded { + if statusBarStyleUpdated { + self.validStatusBarStyle = self.theme.statusBar + let normalStatusBarStyle: UIStatusBarStyle + switch self.theme.statusBar { + case .black: + normalStatusBarStyle = .default + case .white: + normalStatusBarStyle = .lightContent + } + self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false) + } + self.controllerView.backgroundColor = theme.emptyAreaColor + } + } + + open func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { + return nil + } + + public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + if !self.isViewLoaded { + self.loadView() + } + self.validLayout = layout + transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) + self.updateContainers(layout: layout, transition: transition) + } + + private func updateContainers(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let navigationLayout = makeNavigationLayout(layout: layout, controllers: self._viewControllers) + + var transition = transition + + var modalContainers: [NavigationModalContainer] = [] + for i in 0 ..< navigationLayout.modal.count { + var existingModalContainer: NavigationModalContainer? + loop: for currentModalContainer in self.modalContainers { + for controller in navigationLayout.modal[i].controllers { + if currentModalContainer.container.controllers.contains(where: { $0 === controller }) { + existingModalContainer = currentModalContainer + break loop + } + } + } + + let modalContainer: NavigationModalContainer + let containerTransition: ContainedViewLayoutTransition + if let existingModalContainer = existingModalContainer { + modalContainer = existingModalContainer + containerTransition = transition + } else { + modalContainer = NavigationModalContainer(theme: self.theme, controllerRemoved: { [weak self] controller in + self?.controllerRemoved(controller) + }) + self.modalContainers.append(modalContainer) + if !modalContainer.isReady { + modalContainer.isReadyUpdated = { [weak self, weak modalContainer] in + guard let strongSelf = self, let modalContainer = modalContainer else { + return + } + if let layout = strongSelf.validLayout { + strongSelf.updateContainers(layout: layout, transition: .animated(duration: 0.5, curve: .spring)) + } + } + } + modalContainer.updateDismissProgress = { [weak self, weak modalContainer] _ in + guard let strongSelf = self, let modalContainer = modalContainer else { + return + } + if let layout = strongSelf.validLayout { + strongSelf.updateContainers(layout: layout, transition: .immediate) + } + } + modalContainer.interactivelyDismissed = { [weak self, weak modalContainer] in + guard let strongSelf = self, let modalContainer = modalContainer else { + return + } + let controllers = strongSelf._viewControllers.filter { controller in + return !modalContainer.container.controllers.contains(where: { $0 === controller }) + } + strongSelf.setViewControllers(controllers, animated: false) + } + containerTransition = .immediate + } + + containerTransition.updateFrame(node: modalContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) + modalContainer.update(layout: layout, controllers: navigationLayout.modal[i].controllers, transition: containerTransition) + modalContainers.append(modalContainer) + } + + for container in self.modalContainers { + if !modalContainers.contains(where: { $0 === container }) { + transition = container.dismiss(transition: transition, completion: { [weak container] in + container?.removeFromSupernode() + }) + } + } + self.modalContainers = modalContainers + + var previousModalContainer: NavigationModalContainer? + var visibleModalCount = 0 + var topModalDismissProgress: CGFloat = 0.0 + + for i in (0 ..< navigationLayout.modal.count).reversed() { + let modalContainer = self.modalContainers[i] + if modalContainer.supernode == nil && modalContainer.isReady { + if let previousModalContainer = previousModalContainer { + self.displayNode.insertSubnode(modalContainer, belowSubnode: previousModalContainer) + } else { + self.displayNode.addSubnode(modalContainer) + } + modalContainer.animateIn(transition: transition) + } + if modalContainer.supernode != nil { + visibleModalCount += 1 + if previousModalContainer == nil { + topModalDismissProgress = modalContainer.dismissProgress + modalContainer.container.keyboardManager = self.keyboardManager + } else { + modalContainer.container.keyboardManager = nil + } + previousModalContainer = modalContainer + } + } + + switch navigationLayout.root { + case let .flat(controllers): + if let rootContainer = self.rootContainer { + switch rootContainer { + case let .flat(flatContainer): + if previousModalContainer == nil { + flatContainer.keyboardManager = self.keyboardManager + } else { + flatContainer.keyboardManager = nil + } + transition.updateFrame(node: flatContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) + flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: transition) + case let .split(splitContainer): + let flatContainer = NavigationContainer(controllerRemoved: { [weak self] controller in + self?.controllerRemoved(controller) + }) + self.displayNode.insertSubnode(flatContainer, at: 0) + self.rootContainer = .flat(flatContainer) + flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size) + flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: .immediate) + splitContainer.removeFromSupernode() + } + } else { + let flatContainer = NavigationContainer(controllerRemoved: { [weak self] controller in + self?.controllerRemoved(controller) + }) + self.displayNode.insertSubnode(flatContainer, at: 0) + self.rootContainer = .flat(flatContainer) + flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size) + flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: .immediate) + } + case let .split(masterControllers, detailControllers): + if let rootContainer = self.rootContainer { + switch rootContainer { + case let .flat(flatContainer): + let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in + self?.controllerRemoved(controller) + }) + self.displayNode.insertSubnode(splitContainer, at: 0) + self.rootContainer = .split(splitContainer) + splitContainer.frame = CGRect(origin: CGPoint(), size: layout.size) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: .immediate) + flatContainer.removeFromSupernode() + case let .split(splitContainer): + transition.updateFrame(node: splitContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: transition) + } + } else { + let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in + self?.controllerRemoved(controller) + }) + self.displayNode.insertSubnode(splitContainer, at: 0) + self.rootContainer = .split(splitContainer) + splitContainer.frame = CGRect(origin: CGPoint(), size: layout.size) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: .immediate) + } + } + + switch layout.metrics.widthClass { + case .compact: + if visibleModalCount != 0 { + let rootModalFrame: NavigationModalFrame + var modalFrameTransition: ContainedViewLayoutTransition = transition + var forceStatusBarAnimation = false + if let current = self.rootModalFrame { + rootModalFrame = current + transition.updateFrame(node: rootModalFrame, frame: CGRect(origin: CGPoint(), size: layout.size)) + rootModalFrame.update(layout: layout, transition: modalFrameTransition) + rootModalFrame.updateDismissal(transition: transition, progress: topModalDismissProgress, completion: {}) + forceStatusBarAnimation = true + } else { + rootModalFrame = NavigationModalFrame(theme: self.theme) + self.rootModalFrame = rootModalFrame + rootModalFrame.frame = CGRect(origin: CGPoint(), size: layout.size) + rootModalFrame.update(layout: layout, transition: .immediate) + rootModalFrame.animateIn(transition: transition) + if let rootContainer = self.rootContainer { + var rootContainerNode: ASDisplayNode + switch rootContainer { + case let .flat(container): + rootContainerNode = container + case let .split(container): + rootContainerNode = container + } + self.displayNode.insertSubnode(rootModalFrame, aboveSubnode: rootContainerNode) + } + rootModalFrame.updateDismissal(transition: transition, progress: topModalDismissProgress, completion: {}) + } + if topModalDismissProgress < 0.5 { + self.statusBarHost?.setStatusBarStyle(.lightContent, animated: transition.isAnimated || forceStatusBarAnimation) + } else { + let normalStatusBarStyle: UIStatusBarStyle + switch self.theme.statusBar { + case .black: + normalStatusBarStyle = .default + case .white: + normalStatusBarStyle = .lightContent + } + self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: transition.isAnimated || forceStatusBarAnimation) + } + if let rootContainer = self.rootContainer { + var rootContainerNode: ASDisplayNode + switch rootContainer { + case let .flat(container): + rootContainerNode = container + case let .split(container): + rootContainerNode = container + } + let maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width + var topInset: CGFloat = 0.0 + if let statusBarHeight = layout.statusBarHeight { + topInset += statusBarHeight + } + let maxOffset: CGFloat = (topInset - (layout.size.height - layout.size.height * maxScale) / 2.0) + + let scale = 1.0 * topModalDismissProgress + (1.0 - topModalDismissProgress) * maxScale + let offset = (1.0 - topModalDismissProgress) * maxOffset + transition.updateSublayerTransformScaleAndOffset(node: rootContainerNode, scale: scale, offset: CGPoint(x: 0.0, y: offset)) + } + } else { + if let rootModalFrame = self.rootModalFrame { + self.rootModalFrame = nil + rootModalFrame.updateDismissal(transition: transition, progress: 1.0, completion: { [weak rootModalFrame] in + rootModalFrame?.removeFromSupernode() + }) + let normalStatusBarStyle: UIStatusBarStyle + switch self.theme.statusBar { + case .black: + normalStatusBarStyle = .default + case .white: + normalStatusBarStyle = .lightContent + } + self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: transition.isAnimated) + } + if let rootContainer = self.rootContainer { + var rootContainerNode: ASDisplayNode + switch rootContainer { + case let .flat(container): + rootContainerNode = container + case let .split(container): + rootContainerNode = container + } + transition.updateSublayerTransformScaleAndOffset(node: rootContainerNode, scale: 1.0, offset: CGPoint()) + } + } + case .regular: + if let rootModalFrame = self.rootModalFrame { + self.rootModalFrame = nil + rootModalFrame.updateDismissal(transition: .immediate, progress: 1.0, completion: { [weak rootModalFrame] in + rootModalFrame?.removeFromSupernode() + }) + let normalStatusBarStyle: UIStatusBarStyle + switch self.theme.statusBar { + case .black: + normalStatusBarStyle = .default + case .white: + normalStatusBarStyle = .lightContent + } + self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false) + } + if let rootContainer = self.rootContainer { + var rootContainerNode: ASDisplayNode + switch rootContainer { + case let .flat(container): + rootContainerNode = container + case let .split(container): + rootContainerNode = container + } + ContainedViewLayoutTransition.immediate.updateSublayerTransformScaleAndOffset(node: rootContainerNode, scale: 1.0, offset: CGPoint()) + } + } + + if self.validStatusBarStyle != self.theme.statusBar { + self.validStatusBarStyle = self.theme.statusBar + let normalStatusBarStyle: UIStatusBarStyle + switch self.theme.statusBar { + case .black: + normalStatusBarStyle = .default + case .white: + normalStatusBarStyle = .lightContent + } + self.statusBarHost?.setStatusBarStyle(normalStatusBarStyle, animated: false) + } + } + + private func controllerRemoved(_ controller: ViewController) { + self.filterController(controller, animated: false) + } + + public func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) { + } + + public func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) { + /*for record in self._viewControllers { + if let controller = record.controller as? ContainableController { + controller.updateToInterfaceOrientation(orientation) + } + }*/ + } + + open override func loadView() { + self._displayNode = ASDisplayNode(viewBlock: { + return NavigationControllerView() + }, didLoad: nil) + + self.view = self.displayNode.view + self.view.clipsToBounds = true + self.view.autoresizingMask = [] + + self.controllerView.backgroundColor = self.theme.emptyAreaColor + + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.navigationBar.prefersLargeTitles = false + } + self.navigationBar.removeFromSuperview() + + /*let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + panRecognizer.delegate = self + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = true + self.view.addGestureRecognizer(panRecognizer)*/ + } + + public func pushViewController(_ controller: ViewController) { + self.pushViewController(controller, completion: {}) + } + + public func pushViewController(_ controller: ViewController, animated: Bool = true, completion: @escaping () -> Void) { + let navigateAction: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + + if !controller.hasActiveInput { + //strongSelf.view.endEditing(true) + } + /*strongSelf.scheduleAfterLayout({ + guard let strongSelf = self else { + return + }*/ + strongSelf.pushViewController(controller, animated: animated) + completion() + //}) + } + + /*if let lastController = self.viewControllers.last as? ViewController, !lastController.attemptNavigation(navigateAction) { + } else {*/ + navigateAction() + //} + } + + open override func pushViewController(_ viewController: UIViewController, animated: Bool) { + var controllers = self.viewControllers + controllers.append(viewController) + self.setViewControllers(controllers, animated: animated) + } + + public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { + ready?.set(true) + var controllers = self.viewControllers + controllers.removeLast() + controllers.append(controller) + self.setViewControllers(controllers, animated: animated) + } + + public func filterController(_ controller: ViewController, animated: Bool) { + let controllers = self.viewControllers.filter({ $0 !== controller }) + if controllers.count != self.viewControllers.count { + self.setViewControllers(controllers, animated: animated) + } + } + + public func replaceControllersAndPush(controllers: [UIViewController], controller: ViewController, animated: Bool, options: NavigationAnimationOptions = [], ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { + ready?.set(true) + var controllers = controllers + controllers.append(controller) + self.setViewControllers(controllers, animated: animated) + completion() + } + + public func replaceAllButRootController(_ controller: ViewController, animated: Bool, animationOptions: NavigationAnimationOptions = [], ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { + ready?.set(true) + var controllers = self.viewControllers + while controllers.count > 1 { + controllers.removeLast() + } + controllers.append(controller) + self.setViewControllers(controllers, animated: animated) + completion() + } + + public func popToRoot(animated: Bool) { + var controllers = self.viewControllers + while controllers.count > 1 { + controllers.removeLast() + } + self.setViewControllers(controllers, animated: animated) + } + + override open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { + var poppedControllers: [UIViewController] = [] + var found = false + var controllers = self.viewControllers + if !controllers.contains(where: { $0 === viewController }) { + return nil + } + while !controllers.isEmpty { + if controllers[controllers.count - 1] === viewController { + found = true + break + } + poppedControllers.insert(controllers[controllers.count - 1], at: 0) + controllers.removeLast() + } + if found { + self.setViewControllers(controllers, animated: animated) + return poppedControllers + } else { + return nil + } + } + + open override func popViewController(animated: Bool) -> UIViewController? { + var controller: UIViewController? + var controllers = self.viewControllers + if controllers.count != 0 { + controller = controllers[controllers.count - 1] as UIViewController + controllers.remove(at: controllers.count - 1) + self.setViewControllers(controllers, animated: animated) + } + return controller + } + + open override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + self._viewControllers = viewControllers.map { controller in + let controller = controller as! ViewController + controller.navigation_setNavigationController(self) + return controller + } + if let layout = self.validLayout { + self.updateContainers(layout: layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) + } + } + + override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + preconditionFailure() + /*if let controller = viewControllerToPresent as? NavigationController { + controller.navigation_setDismiss({ [weak self] in + if let strongSelf = self { + strongSelf.dismiss(animated: false, completion: nil) + } + }, rootController: self.view!.window!.rootViewController) + self._presentedViewController = controller + + self.view.endEditing(true) + if let validLayout = self.validLayout { + controller.containerLayoutUpdated(validLayout, transition: .immediate) + } + + var ready: Signal = .single(true) + + if let controller = controller.topViewController as? ViewController { + ready = controller.ready.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue + } + + self.currentPresentDisposable.set(ready.start(next: { [weak self] _ in + if let strongSelf = self { + if flag { + controller.view.frame = strongSelf.view.bounds.offsetBy(dx: 0.0, dy: strongSelf.view.bounds.height) + strongSelf.view.addSubview(controller.view) + UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: { + controller.view.frame = strongSelf.view.bounds + }, completion: { _ in + if let completion = completion { + completion() + } + }) + } else { + controller.view.frame = strongSelf.view.bounds + strongSelf.view.addSubview(controller.view) + + if let completion = completion { + completion() + } + } + } + })) + } else { + preconditionFailure("NavigationController can't present \(viewControllerToPresent). Only subclasses of NavigationController are allowed.") + }*/ + } + + override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + if let controller = self.presentedViewController { + if flag { + UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: { + controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) + }, completion: { _ in + controller.view.removeFromSuperview() + self._presentedViewController = nil + if let completion = completion { + completion() + } + }) + } else { + controller.view.removeFromSuperview() + self._presentedViewController = nil + if let completion = completion { + completion() + } + } + } + } + + public final var currentWindow: WindowHost? { + if let window = self.view.window as? WindowHost { + return window + } else if let superwindow = self.view.window { + for subview in superwindow.subviews { + if let subview = subview as? WindowHost { + return subview + } + } + } + return nil + } + + private func scheduleAfterLayout(_ f: @escaping () -> Void) { + (self.view as? UITracingLayerView)?.schedule(layout: { + f() + }) + self.view.setNeedsLayout() + } + + private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) { + let requestId = self.scheduledLayoutTransitionRequestId + self.scheduledLayoutTransitionRequestId += 1 + self.scheduledLayoutTransitionRequest = (requestId, transition) + (self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in + if let strongSelf = self { + if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId { + strongSelf.scheduledLayoutTransitionRequest = nil + strongSelf.requestLayout(transition: currentRequestTransition) + } + } + }) + self.view.setNeedsLayout() + } + + private func requestLayout(transition: ContainedViewLayoutTransition) { + if self.isViewLoaded, let validLayout = self.validLayout { + self.containerLayoutUpdated(validLayout, transition: transition) + } + } +} diff --git a/submodules/Display/Display/Navigation/NavigationLayout.swift b/submodules/Display/Display/Navigation/NavigationLayout.swift new file mode 100644 index 0000000000..c2cf8242e5 --- /dev/null +++ b/submodules/Display/Display/Navigation/NavigationLayout.swift @@ -0,0 +1,76 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit + +enum RootNavigationLayout { + case split([ViewController], [ViewController]) + case flat([ViewController]) +} + +struct ModalContainerLayout { + var controllers: [ViewController] +} + +struct NavigationLayout { + var root: RootNavigationLayout + var modal: [ModalContainerLayout] +} + +func makeNavigationLayout(layout: ContainerViewLayout, controllers: [ViewController]) -> NavigationLayout { + var rootControllers: [ViewController] = [] + var modalStack: [ModalContainerLayout] = [] + for controller in controllers { + let requiresModal: Bool + var beginsModal: Bool = false + switch controller.navigationPresentation { + case .default: + requiresModal = false + case .master: + requiresModal = false + case .modal: + requiresModal = true + beginsModal = true + case .modalInLargeLayout: + switch layout.metrics.widthClass { + case .compact: + requiresModal = false + case .regular: + requiresModal = true + } + } + if requiresModal { + if beginsModal || modalStack.isEmpty { + modalStack.append(ModalContainerLayout(controllers: [controller])) + } else { + modalStack[modalStack.count - 1].controllers.append(controller) + } + } else if !modalStack.isEmpty { + modalStack[modalStack.count - 1].controllers.append(controller) + } else { + rootControllers.append(controller) + } + } + let rootLayout: RootNavigationLayout + switch layout.metrics.widthClass { + case .compact: + rootLayout = .flat(rootControllers) + case .regular: + let masterControllers = rootControllers.filter { + if case .master = $0.navigationPresentation { + return true + } else { + return false + } + } + let detailControllers = rootControllers.filter { + if case .master = $0.navigationPresentation { + return false + } else { + return true + } + } + rootLayout = .split(masterControllers, detailControllers) + } + return NavigationLayout(root: rootLayout, modal: modalStack) +} diff --git a/submodules/Display/Display/Navigation/NavigationModalContainer.swift b/submodules/Display/Display/Navigation/NavigationModalContainer.swift new file mode 100644 index 0000000000..92161210af --- /dev/null +++ b/submodules/Display/Display/Navigation/NavigationModalContainer.swift @@ -0,0 +1,196 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit + +final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate { + private var theme: NavigationControllerTheme + + private let dim: ASDisplayNode + private let scrollNode: ASScrollNode + let container: NavigationContainer + + private(set) var isReady: Bool = false + private(set) var dismissProgress: CGFloat = 0.0 + var isReadyUpdated: (() -> Void)? + var updateDismissProgress: ((CGFloat) -> Void)? + var interactivelyDismissed: (() -> Void)? + + private var ignoreScrolling = false + private var animator: DisplayLinkAnimator? + + init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) { + self.theme = theme + + self.dim = ASDisplayNode() + self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4) + self.dim.alpha = 0.0 + + self.scrollNode = ASScrollNode() + + self.container = NavigationContainer(controllerRemoved: controllerRemoved) + self.container.clipsToBounds = true + + super.init() + + self.addSubnode(self.dim) + self.addSubnode(self.scrollNode) + self.scrollNode.addSubnode(self.container) + + self.isReady = self.container.isReady + self.container.isReadyUpdated = { [weak self] in + guard let strongSelf = self else { + return + } + if !strongSelf.isReady { + strongSelf.isReady = true + strongSelf.isReadyUpdated?() + } + } + } + + deinit { + self.animator?.invalidate() + } + + override func didLoad() { + super.didLoad() + + self.scrollNode.view.alwaysBounceVertical = false + self.scrollNode.view.alwaysBounceHorizontal = false + self.scrollNode.view.bounces = false + self.scrollNode.view.showsVerticalScrollIndicator = false + self.scrollNode.view.showsHorizontalScrollIndicator = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + } + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.delegate = self + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if self.ignoreScrolling { + return + } + var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height + progress = max(0.0, min(1.0, progress)) + self.dismissProgress = progress + self.dim.alpha = 1.0 - progress + self.updateDismissProgress?(progress) + } + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + var progress = (self.bounds.height - scrollView.bounds.origin.y) / self.bounds.height + progress = max(0.0, min(1.0, progress)) + + self.ignoreScrolling = true + targetContentOffset.pointee = scrollView.contentOffset + scrollView.setContentOffset(scrollView.contentOffset, animated: false) + self.ignoreScrolling = false + self.animator?.invalidate() + let targetOffset: CGFloat + if velocity.y < -0.5 || progress >= 0.5 { + targetOffset = 0.0 + } else { + targetOffset = self.bounds.height + } + let velocityFactor: CGFloat = 0.4 / max(1.0, abs(velocity.y)) + self.animator = DisplayLinkAnimator(duration: Double(min(0.3, velocityFactor)), from: scrollView.contentOffset.y, to: targetOffset, update: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: value) + }, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.animator = nil + if targetOffset == 0.0 { + strongSelf.interactivelyDismissed?() + } + }) + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + } + + func update(layout: ContainerViewLayout, controllers: [ViewController], transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: layout.size.height * 2.0) + if !self.scrollNode.view.isDecelerating && !self.scrollNode.view.isDragging && self.animator == nil { + transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: layout.size)) + } + + let containerLayout: ContainerViewLayout + let containerFrame: CGRect + switch layout.metrics.widthClass { + case .compact: + self.dim.isHidden = true + self.container.clipsToBounds = true + self.container.cornerRadius = 10.0 + if #available(iOS 11.0, *) { + self.container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + } + + var topInset: CGFloat = 10.0 + if let statusBarHeight = layout.statusBarHeight { + topInset += statusBarHeight + } + + containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: layout.intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.safeInsets.bottom, right: layout.safeInsets.right), statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) + containerFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: containerLayout.size) + case .regular: + self.dim.isHidden = false + self.container.clipsToBounds = true + self.container.cornerRadius = 10.0 + if #available(iOS 11.0, *) { + self.container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] + } + + let verticalInset: CGFloat = 44.0 + + let maxSide = max(layout.size.width, layout.size.height) + let containerSize = CGSize(width: max(layout.size.width - 20.0, floor(maxSide / 2.0)), height: layout.size.height - verticalInset * 2.0) + containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) + + var inputHeight: CGFloat? + if let inputHeightValue = layout.inputHeight { + inputHeight = max(0.0, inputHeightValue - (layout.size.height - containerFrame.maxY)) + } + containerLayout = ContainerViewLayout(size: containerSize, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) + } + transition.updateFrame(node: self.container, frame: containerFrame.offsetBy(dx: 0.0, dy: layout.size.height)) + self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition) + } + + func animateIn(transition: ContainedViewLayoutTransition) { + transition.updateAlpha(node: self.dim, alpha: 1.0) + transition.animatePositionAdditive(node: self.container, offset: CGPoint(x: 0.0, y: self.bounds.height + self.container.bounds.height / 2.0 - (self.container.position.y - self.bounds.height))) + } + + func dismiss(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) -> ContainedViewLayoutTransition { + for controller in self.container.controllers { + controller.viewWillDisappear(transition.isAnimated) + } + + if transition.isAnimated { + let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + alphaTransition.updateAlpha(node: self.dim, alpha: 0.0, beginWithCurrentState: true) + positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0 + self.bounds.height), beginWithCurrentState: true, completion: { [weak self] _ in + completion() + }) + return positionTransition + } else { + for controller in self.container.controllers { + controller.setIgnoreAppearanceMethodInvocations(true) + controller.displayNode.removeFromSupernode() + controller.setIgnoreAppearanceMethodInvocations(false) + controller.viewDidDisappear(transition.isAnimated) + } + completion() + return transition + } + } +} diff --git a/submodules/Display/Display/Navigation/NavigationModalFrame.swift b/submodules/Display/Display/Navigation/NavigationModalFrame.swift new file mode 100644 index 0000000000..6155fd3764 --- /dev/null +++ b/submodules/Display/Display/Navigation/NavigationModalFrame.swift @@ -0,0 +1,113 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit + +private func generateCornerImage(radius: CGFloat, mirror: Bool) -> UIImage? { + return generateImage(CGSize(width: radius, height: radius), rotatedContext: { size, context in + context.setFillColor(UIColor.black.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: mirror ? (-radius) : 0.0, y: 0.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + }) +} + +final class NavigationModalFrame: ASDisplayNode { + private let dim: ASDisplayNode + private let topShade: ASDisplayNode + private let leftShade: ASDisplayNode + private let rightShade: ASDisplayNode + private let topLeftCorner: ASImageNode + private let topRightCorner: ASImageNode + + private var currentMaxCornerRadius: CGFloat? + + private var progress: CGFloat = 0.0 + private var validLayout: ContainerViewLayout? + + init(theme: NavigationControllerTheme) { + self.dim = ASDisplayNode() + self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4) + self.dim.alpha = 0.0 + + self.topShade = ASDisplayNode() + self.topShade.backgroundColor = .black + self.leftShade = ASDisplayNode() + self.leftShade.backgroundColor = .black + self.rightShade = ASDisplayNode() + self.rightShade.backgroundColor = .black + + self.topLeftCorner = ASImageNode() + self.topLeftCorner.displaysAsynchronously = false + self.topLeftCorner.displayWithoutProcessing = true + self.topRightCorner = ASImageNode() + self.topRightCorner.displaysAsynchronously = false + self.topRightCorner.displayWithoutProcessing = true + + super.init() + + self.addSubnode(self.dim) + self.addSubnode(self.topShade) + self.addSubnode(self.leftShade) + self.addSubnode(self.rightShade) + self.addSubnode(self.topLeftCorner) + self.addSubnode(self.topRightCorner) + } + + func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size)) + + self.updateShades(layout: layout, progress: 1.0 - self.progress, transition: transition) + } + + func animateIn(transition: ContainedViewLayoutTransition) { + transition.updateAlpha(node: self.dim, alpha: 1.0) + + if let layout = self.validLayout { + self.updateShades(layout: layout, progress: 0.0, transition: .immediate) + self.updateShades(layout: layout, progress: 1.0, transition: transition) + } + } + + func updateDismissal(transition: ContainedViewLayoutTransition, progress: CGFloat, completion: @escaping () -> Void) { + self.progress = progress + + transition.updateAlpha(node: self.dim, alpha: 1.0 - progress, completion: { _ in + completion() + }) + if let layout = self.validLayout { + self.updateShades(layout: layout, progress: 1.0 - progress, transition: transition) + } + } + + private func updateShades(layout: ContainerViewLayout, progress: CGFloat, transition: ContainedViewLayoutTransition) { + let sideInset: CGFloat = 16.0 + var topInset: CGFloat = 0.0 + if let statusBarHeight = layout.statusBarHeight { + topInset += statusBarHeight + } + + let cornerRadius: CGFloat = 8.0 + let initialCornerRadius: CGFloat + if !layout.safeInsets.top.isZero { + initialCornerRadius = 40.0 + } else { + initialCornerRadius = 0.0 + } + if self.currentMaxCornerRadius != cornerRadius { + self.topLeftCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), mirror: false) + self.topRightCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), mirror: true) + } + + let cornerSize = progress * cornerRadius + (1.0 - progress) * initialCornerRadius + transition.updateFrame(node: self.topLeftCorner, frame: CGRect(origin: CGPoint(x: progress * sideInset, y: progress * topInset), size: CGSize(width: cornerSize, height: cornerSize))) + transition.updateFrame(node: self.topRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - progress * sideInset - cornerSize, y: progress * topInset), size: CGSize(width: cornerSize, height: cornerSize))) + + transition.updateFrame(node: self.topShade, frame: CGRect(origin: CGPoint(x: 0.0, y: (1.0 - progress) * (-topInset)), size: CGSize(width: layout.size.width, height: topInset))) + transition.updateFrame(node: self.leftShade, frame: CGRect(origin: CGPoint(x: (1.0 - progress) * (-sideInset), y: 0.0), size: CGSize(width: sideInset, height: layout.size.height))) + transition.updateFrame(node: self.rightShade, frame: CGRect(origin: CGPoint(x: layout.size.width - sideInset * progress, y: 0.0), size: CGSize(width: sideInset, height: layout.size.height))) + } +} diff --git a/submodules/Display/Display/Navigation/NavigationSplitContainer.swift b/submodules/Display/Display/Navigation/NavigationSplitContainer.swift new file mode 100644 index 0000000000..b9f3b711d2 --- /dev/null +++ b/submodules/Display/Display/Navigation/NavigationSplitContainer.swift @@ -0,0 +1,43 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit + +final class NavigationSplitContainer: ASDisplayNode { + private var theme: NavigationControllerTheme + + private let masterContainer: NavigationContainer + private let detailContainer: NavigationContainer + private let separator: ASDisplayNode + + init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) { + self.theme = theme + + self.masterContainer = NavigationContainer(controllerRemoved: controllerRemoved) + self.masterContainer.clipsToBounds = true + + self.detailContainer = NavigationContainer(controllerRemoved: controllerRemoved) + self.detailContainer.clipsToBounds = true + + self.separator = ASDisplayNode() + self.separator.backgroundColor = theme.navigationBar.separatorColor + + super.init() + + self.addSubnode(self.masterContainer) + self.addSubnode(self.detailContainer) + self.addSubnode(self.separator) + } + + func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], transition: ContainedViewLayoutTransition) { + let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) + let detailWidth = layout.size.width - masterWidth + + transition.updateFrame(node: self.masterContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: masterWidth, height: layout.size.height))) + transition.updateFrame(node: self.detailContainer, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height))) + transition.updateFrame(node: self.separator, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) + + self.masterContainer.update(layout: ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: false, controllers: masterControllers, transition: transition) + self.detailContainer.update(layout: ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: true, controllers: detailControllers, transition: transition) + } +} diff --git a/submodules/Display/Display/NavigationController.swift b/submodules/Display/Display/NavigationController.swift deleted file mode 100644 index 9aa22ec343..0000000000 --- a/submodules/Display/Display/NavigationController.swift +++ /dev/null @@ -1,1689 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import SwiftSignalKit - -public final class NavigationControllerTheme { - public let navigationBar: NavigationBarTheme - public let emptyAreaColor: UIColor - - public init(navigationBar: NavigationBarTheme, emptyAreaColor: UIColor) { - self.navigationBar = navigationBar - self.emptyAreaColor = emptyAreaColor - } -} - -public struct NavigationAnimationOptions : OptionSet { - public let rawValue: Int - - public init(rawValue: Int) { - self.rawValue = rawValue - } - - public init() { - self.rawValue = 0 - } - - public static let removeOnMasterDetails = NavigationAnimationOptions(rawValue: 1 << 0) -} - -private final class NavigationControllerContainerView: UIView { - override class var layerClass: AnyClass { - return CATracingLayer.self - } -} - -public enum NavigationEmptyDetailsBackgoundMode { - case image(UIImage) - case wallpaper(UIImage) -} - -private final class NavigationControllerView: UITracingLayerView { - var inTransition = false - - let sharedStatusBar: StatusBar - let masterContainerView: NavigationControllerContainerView - let containerView: NavigationControllerContainerView - let separatorView: UIView - var navigationBackgroundView: UIView? - var navigationSeparatorView: UIView? - var emptyDetailView: UIImageView? - var detailsBackground: WallpaperBackgroundNode? - var masterDetailsBlackout: ASDisplayNode? - var topControllerNode: ASDisplayNode? - - override init(frame: CGRect) { - self.masterContainerView = NavigationControllerContainerView() - self.containerView = NavigationControllerContainerView() - self.separatorView = UIView() - self.sharedStatusBar = StatusBar() - - super.init(frame: frame) - - self.addSubview(self.masterContainerView) - self.addSubview(self.containerView) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override class var layerClass: AnyClass { - return CATracingLayer.self - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.bounds.contains(point) && self.inTransition { - return self - } - return super.hitTest(point, with: event) - } -} - -private enum ControllerTransition { - case none - case appearance -} - -private final class ControllerRecord { - let controller: UIViewController - var transition: ControllerTransition = .none - - init(controller: UIViewController) { - self.controller = controller - } -} - -private enum ControllerLayoutConfiguration { - case single - case masterDetail -} - -public enum NavigationControllerMode { - case single - case automaticMasterDetail -} - -public enum MasterDetailLayoutBlackout : Equatable { - case master - case details -} - -open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { - public var isOpaqueWhenInOverlay: Bool = true - public var blocksBackgroundWhenInOverlay: Bool = true - public var isModalWhenInOverlay: Bool = false - public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)? - - private let _ready = Promise(true) - open var ready: Promise { - return self._ready - } - - private var masterDetailsBlackout: MasterDetailLayoutBlackout? - private var backgroundDetailsMode: NavigationEmptyDetailsBackgoundMode? - - public var lockOrientation: Bool = false - - public var deferScreenEdgeGestures: UIRectEdge = UIRectEdge() - - private let mode: NavigationControllerMode - private var theme: NavigationControllerTheme - - public private(set) weak var overlayPresentingController: ViewController? - - private var controllerView: NavigationControllerView { - return self.view as! NavigationControllerView - } - - private var validLayout: ContainerViewLayout? - - private var scheduledLayoutTransitionRequestId: Int = 0 - private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)? - - private var masterTransitionCoordinator: NavigationTransitionCoordinator? - private var navigationTransitionCoordinator: NavigationTransitionCoordinator? - - private var currentPushDisposable = MetaDisposable() - private var currentPresentDisposable = MetaDisposable() - - private var _presentedViewController: UIViewController? - open override var presentedViewController: UIViewController? { - return self._presentedViewController - } - - private var _viewControllers: [ControllerRecord] = [] - override open var viewControllers: [UIViewController] { - get { - return self._viewControllers.map { $0.controller } - } set(value) { - self.setViewControllers(value, animated: false) - } - } - - override open var topViewController: UIViewController? { - return self._viewControllers.last?.controller - } - - private var _displayNode: ASDisplayNode? - public var displayNode: ASDisplayNode { - return self._displayNode! - } - - public func updateMasterDetailsBlackout(_ blackout: MasterDetailLayoutBlackout?, transition: ContainedViewLayoutTransition) { - self.masterDetailsBlackout = blackout - if isViewLoaded { - self.view.endEditing(true) - } - self.requestLayout(transition: transition) - } - public func updateBackgroundDetailsMode(_ mode: NavigationEmptyDetailsBackgoundMode?, transition: ContainedViewLayoutTransition) { - self.backgroundDetailsMode = mode - self.requestLayout(transition: transition) - } - - - public init(mode: NavigationControllerMode, theme: NavigationControllerTheme, backgroundDetailsMode: NavigationEmptyDetailsBackgoundMode? = nil) { - self.mode = mode - self.theme = theme - self.backgroundDetailsMode = backgroundDetailsMode - super.init(nibName: nil, bundle: nil) - } - - public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - preconditionFailure() - } - - public required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.currentPushDisposable.dispose() - self.currentPresentDisposable.dispose() - } - - public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { - var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) - if let controller = self.viewControllers.last { - if let controller = controller as? ViewController { - if controller.lockOrientation { - if let lockedOrientation = controller.lockedOrientation { - supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: lockedOrientation, compactSize: lockedOrientation)) - } else { - supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: currentOrientationToLock, compactSize: currentOrientationToLock)) - } - } else { - supportedOrientations = supportedOrientations.intersection(controller.supportedOrientations) - } - } - } - return supportedOrientations - } - - public func updateTheme(_ theme: NavigationControllerTheme) { - self.theme = theme - if self.isViewLoaded { - self.controllerView.backgroundColor = theme.emptyAreaColor - self.controllerView.separatorView.backgroundColor = theme.navigationBar.separatorColor - self.controllerView.navigationBackgroundView?.backgroundColor = theme.navigationBar.backgroundColor - self.controllerView.navigationSeparatorView?.backgroundColor = theme.navigationBar.separatorColor - } - } - - private var previouslyLaidOutMasterController: UIViewController? - private var previouslyLaidOutTopController: UIViewController? - - open func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { - return nil - } - - private func layoutConfiguration(for layout: ContainerViewLayout) -> ControllerLayoutConfiguration { - switch self.mode { - case .single: - return .single - case .automaticMasterDetail: - if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass { - if layout.size.width > 690.0 { - return .masterDetail - } - } - return .single - } - } - - private func layoutDataForConfiguration(_ layoutConfiguration: ControllerLayoutConfiguration, layout: ContainerViewLayout, index: Int) -> (CGRect, ContainerViewLayout) { - switch layoutConfiguration { - case .masterDetail: - let masterWidth: CGFloat = max(320.0, min(375.0, floor(layout.size.width / 3.0))) - let detailWidth: CGFloat = layout.size.width - masterWidth - if index == 0 { - return (CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: masterWidth, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)) - } else { - let detailFrame = CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height)) - return (CGRect(origin: CGPoint(), size: detailFrame.size), ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: LayoutMetrics(widthClass: .regular, heightClass: .regular), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)) - } - case .single: - var heightClass: ContainerViewLayoutSizeClass - if (layout.size.width < 375.0 && layout.size.height > 700.0) || layout.size.height > 900.0 { - heightClass = .regular - } else { - heightClass = .compact - } - - return (CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height), metrics: LayoutMetrics(widthClass: .compact, heightClass: heightClass), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)) - } - } - - private func updateControllerLayouts(previousControllers: [ControllerRecord], layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - var firstControllerFrameAndLayout: (CGRect, ContainerViewLayout)? - let lastControllerFrameAndLayout: (CGRect, ContainerViewLayout) - - let layoutConfiguration = self.layoutConfiguration(for: layout) - - switch layoutConfiguration { - case .masterDetail: - self.controllerView.masterContainerView.clipsToBounds = true - self.controllerView.containerView.clipsToBounds = true - let masterData = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 0) - firstControllerFrameAndLayout = masterData - lastControllerFrameAndLayout = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 1) - if self.controllerView.separatorView.superview == nil { - self.controllerView.addSubview(self.controllerView.separatorView) - } - - if let backgroundDetailsMode = self.backgroundDetailsMode { - switch backgroundDetailsMode { - case let .image(image): - if let detailsBackground = self.controllerView.detailsBackground { - self.controllerView.detailsBackground = nil - transition.updateAlpha(node: detailsBackground, alpha: 0.0, completion: { [weak detailsBackground] _ in - detailsBackground?.removeFromSupernode() - }) - } - let emptyDetailView: UIImageView - if let emptyView = self.controllerView.emptyDetailView { - emptyDetailView = emptyView - } else { - emptyDetailView = UIImageView() - emptyDetailView.alpha = 0.0 - self.controllerView.emptyDetailView = emptyDetailView - } - emptyDetailView.image = image - if emptyDetailView.superview == nil { - self.controllerView.insertSubview(emptyDetailView, at: 0) - } - transition.updateAlpha(layer: emptyDetailView.layer, alpha: 1.0) - - emptyDetailView.frame = CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size) - - - case let .wallpaper(image): - if let emptyDetailView = self.controllerView.emptyDetailView { - self.controllerView.emptyDetailView = nil - transition.updateAlpha(layer: emptyDetailView.layer, alpha: 0.0, completion: { [weak emptyDetailView] _ in - emptyDetailView?.removeFromSuperview() - }) - } - let detailsBackground: WallpaperBackgroundNode - if let background = self.controllerView.detailsBackground { - detailsBackground = background - } else { - detailsBackground = WallpaperBackgroundNode() - detailsBackground.alpha = 0.0 - self.controllerView.detailsBackground = detailsBackground - } - detailsBackground.image = image - if detailsBackground.supernode == nil { - self.controllerView.insertSubview(detailsBackground.view, at: 0) - } - transition.updateAlpha(node: detailsBackground, alpha: 1.0) - detailsBackground.frame = CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: lastControllerFrameAndLayout.0.size) - } - } else { - if let emptyDetailView = self.controllerView.emptyDetailView { - self.controllerView.emptyDetailView = nil - transition.updateAlpha(layer: emptyDetailView.layer, alpha: 0.0, completion: { [weak emptyDetailView] _ in - emptyDetailView?.removeFromSuperview() - }) - } - if let detailsBackground = self.controllerView.detailsBackground { - self.controllerView.detailsBackground = nil - transition.updateAlpha(node: detailsBackground, alpha: 0.0, completion: { [weak detailsBackground] _ in - detailsBackground?.removeFromSupernode() - }) - } - } - - if let blackout = self.masterDetailsBlackout { - let blackoutFrame: CGRect - switch blackout { - case .details: - blackoutFrame = CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: lastControllerFrameAndLayout.0.size) - case .master: - blackoutFrame = masterData.0 - } - if self.controllerView.masterDetailsBlackout == nil { - self.controllerView.masterDetailsBlackout = ASDisplayNode() - self.controllerView.masterDetailsBlackout?.backgroundColor = UIColor.black - self.controllerView.masterDetailsBlackout?.alpha = 0 - self.controllerView.masterDetailsBlackout?.frame = blackoutFrame - } - let blackoutNode = self.controllerView.masterDetailsBlackout! - if blackoutNode.supernode == nil { - self.controllerView.addSubnode(blackoutNode) - } - transition.updateFrame(node: blackoutNode, frame: blackoutFrame) - transition.updateAlpha(node: blackoutNode, alpha: 0.2) - } else { - if let blackout = self.controllerView.masterDetailsBlackout { - self.controllerView.masterDetailsBlackout = nil - transition.updateAlpha(node: blackout, alpha: 0.0, completion: { [weak blackout] _ in - blackout?.removeFromSupernode() - }) - } - } - - transition.updateFrame(view: self.controllerView.separatorView, frame: CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) - case .single: - self.viewControllers.first?.view.clipsToBounds = false - if let navigationBackgroundView = self.controllerView.navigationBackgroundView, let navigationSeparatorView = self.controllerView.navigationSeparatorView { - self.controllerView.navigationBackgroundView = nil - self.controllerView.navigationSeparatorView = nil - - transition.updatePosition(layer: navigationBackgroundView.layer, position: CGPoint(x: layout.size.width + navigationBackgroundView.bounds.size.width / 2.0, y: navigationBackgroundView.center.y), completion: { [weak navigationBackgroundView] _ in - navigationBackgroundView?.removeFromSuperview() - }) - transition.updatePosition(layer: navigationSeparatorView.layer, position: CGPoint(x: layout.size.width + navigationSeparatorView.bounds.size.width / 2.0, y: navigationSeparatorView.center.y), completion: { [weak navigationSeparatorView] _ in - navigationSeparatorView?.removeFromSuperview() - }) - if let emptyDetailView = self.controllerView.emptyDetailView { - self.controllerView.emptyDetailView = nil - transition.updateAlpha(layer: emptyDetailView.layer, alpha: 0.0, completion: { [weak emptyDetailView] _ in - emptyDetailView?.removeFromSuperview() - }) - } - if let blackout = self.controllerView.masterDetailsBlackout { - self.controllerView.masterDetailsBlackout = nil - transition.updateAlpha(node: blackout, alpha: 0.0, completion: { [weak blackout] _ in - blackout?.removeFromSupernode() - }) - } - } - self.controllerView.masterContainerView.clipsToBounds = false - self.controllerView.containerView.clipsToBounds = false - lastControllerFrameAndLayout = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 1) - transition.updateFrame(view: self.controllerView.separatorView, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)), completion: { [weak self] completed in - if let strongSelf = self, completed { - strongSelf.controllerView.separatorView.removeFromSuperview() - } - }) - } - if let firstControllerFrameAndLayout = firstControllerFrameAndLayout { - transition.updateFrame(view: self.controllerView.masterContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: firstControllerFrameAndLayout.0.size)) - } - transition.updateFrame(view: self.controllerView.containerView, frame: CGRect(origin: CGPoint(x: firstControllerFrameAndLayout?.0.maxX ?? 0.0, y: 0.0), size: lastControllerFrameAndLayout.0.size)) - - switch layoutConfiguration { - case .single: - if self.controllerView.sharedStatusBar.view.superview != nil { - self.controllerView.sharedStatusBar.removeFromSupernode() - self.controllerView.containerView.layer.setTraceableInfo(nil) - - if layout.deviceMetrics.type == .tablet { - self.controllerView.containerView.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: 0, disableChildrenTracingTags: WindowTracingTags.statusBar | WindowTracingTags.keyboard)) - } - } - case .masterDetail: - if self.controllerView.sharedStatusBar.view.superview == nil { - self.controllerView.addSubnode(self.controllerView.sharedStatusBar) - self.controllerView.containerView.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: 0, disableChildrenTracingTags: WindowTracingTags.statusBar | WindowTracingTags.keyboard)) - } - } - - if let _ = layout.statusBarHeight { - self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) - } - - var controllersAndFrames: [(Bool, ControllerRecord, ContainerViewLayout)] = [] - var masterRange: ClosedRange? - if case .masterDetail = layoutConfiguration { - for i in 0 ..< self._viewControllers.count { - if let controller = self._viewControllers[i].controller as? ViewController { - switch controller.navigationPresentation { - case .default: - break - case .master: - if let masterRangeValue = masterRange { - masterRange = masterRangeValue.lowerBound ... i - } else { - masterRange = i ... i - } - } - } - } - } - for i in 0 ..< self._viewControllers.count { - if let controller = self._viewControllers[i].controller as? ViewController { - if i == 0 { - controller.navigationBar?.previousItem = nil - } else if let masterRange = masterRange, i == masterRange.upperBound + 1 { - controller.navigationBar?.previousItem = .close - } else { - controller.navigationBar?.previousItem = .item(self.viewControllers[i - 1].navigationItem) - } - if i < self._viewControllers.count - 1 { - controller.updateNavigationCustomData((self.viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0, transition: transition) - } else { - controller.updateNavigationCustomData(nil, progress: 1.0, transition: transition) - } - } - self.viewControllers[i].navigation_setNavigationController(self) - - if let (_, layout) = firstControllerFrameAndLayout, let controller = self._viewControllers[i].controller as? ViewController, case .master = controller.navigationPresentation { - controllersAndFrames.append((true, self._viewControllers[i], layout)) - } else if i == self._viewControllers.count - 1 { - controllersAndFrames.append((false, self._viewControllers[i], lastControllerFrameAndLayout.1)) - } - } - - while controllersAndFrames.count >= 2 { - if controllersAndFrames[0].0 { - if controllersAndFrames[1].0 { - controllersAndFrames.remove(at: 0) - } else { - break - } - } else { - break - } - } - - var masterController: UIViewController? - var appearingMasterController: ControllerRecord? - var appearingDetailController: ControllerRecord? - - for (isMaster, record, layout) in controllersAndFrames { - let frame: CGRect - if isMaster, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { - masterController = record.controller - frame = firstControllerFrameAndLayout.0 - if let controller = masterController as? ViewController { - self.controllerView.sharedStatusBar.statusBarStyle = controller.statusBar.statusBarStyle - } - } else { - frame = lastControllerFrameAndLayout.0 - } - let isAppearing = record.controller.view.superview == nil - if let controller = record.controller as? ViewController { - let updatedLayout: ContainerViewLayout - if previousControllers.count == self.viewControllers.count + 1, previousControllers[previousControllers.count - 2].controller === controller { - updatedLayout = layout.withUpdatedInputHeight(controller.hasActiveInput ? layout.inputHeight : nil) - } else { - updatedLayout = layout - } - controller.containerLayoutUpdated(updatedLayout, transition: isAppearing ? .immediate : transition) - } - if isAppearing { - if isMaster { - appearingMasterController = record - } else { - appearingDetailController = record - } - } else if record.controller.view.superview !== (isMaster ? self.controllerView.masterContainerView : self.controllerView.containerView) { - record.controller.setIgnoreAppearanceMethodInvocations(true) - if isMaster { - self.controllerView.masterContainerView.addSubview(record.controller.view) - } else { - self.controllerView.containerView.addSubview(record.controller.view) - } - record.controller.setIgnoreAppearanceMethodInvocations(false) - } - var applyFrame = false - if !isAppearing { - applyFrame = true - } else if case .immediate = transition { - applyFrame = true - } - if applyFrame { - var isPartOfTransition = false - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - if navigationTransitionCoordinator.topView == record.controller.view || navigationTransitionCoordinator.bottomView == record.controller.view { - isPartOfTransition = true - } - } - if !isPartOfTransition { - transition.updateFrame(view: record.controller.view, frame: frame) - } - } - } - - var animatedAppearingDetailController = false - var animatedAppearingMasterController = false - - if let previousController = self.previouslyLaidOutMasterController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil { - if transition.isAnimated, let record = appearingMasterController { - animatedAppearingMasterController = true - - previousController.viewWillDisappear(true) - record.controller.viewWillAppear(true) - record.controller.setIgnoreAppearanceMethodInvocations(true) - - if let controller = record.controller as? ViewController, !controller.hasActiveInput { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - } - self.controllerView.masterContainerView.addSubview(record.controller.view) - record.controller.setIgnoreAppearanceMethodInvocations(false) - - if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) { - let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.masterContainerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar) - self.masterTransitionCoordinator = masterTransitionCoordinator - - self.controllerView.inTransition = true - masterTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - strongSelf.masterTransitionCoordinator = nil - strongSelf.controllerView.inTransition = false - - record.controller.viewDidAppear(true) - - previousController.setIgnoreAppearanceMethodInvocations(true) - previousController.view.removeFromSuperview() - previousController.setIgnoreAppearanceMethodInvocations(false) - previousController.viewDidDisappear(true) - } - }) - } else { - if let index = self._viewControllers.firstIndex(where: { $0.controller === previousController }) { - self._viewControllers[index].transition = .appearance - } - let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.masterContainerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar) - self.masterTransitionCoordinator = masterTransitionCoordinator - - self.controllerView.inTransition = true - masterTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - if let index = strongSelf._viewControllers.firstIndex(where: { $0.controller === previousController }) { - strongSelf._viewControllers[index].transition = .none - } - strongSelf.masterTransitionCoordinator = nil - strongSelf.controllerView.inTransition = false - - record.controller.viewDidAppear(true) - - previousController.setIgnoreAppearanceMethodInvocations(true) - previousController.view.removeFromSuperview() - previousController.setIgnoreAppearanceMethodInvocations(false) - previousController.viewDidDisappear(true) - } - }) - } - } else { - previousController.viewWillDisappear(false) - previousController.view.removeFromSuperview() - previousController.viewDidDisappear(false) - } - } - - if let previousController = self.previouslyLaidOutTopController, previousController !== self.previouslyLaidOutMasterController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil { - if transition.isAnimated, let record = appearingDetailController { - animatedAppearingDetailController = true - - previousController.viewWillDisappear(true) - record.controller.viewWillAppear(true) - record.controller.setIgnoreAppearanceMethodInvocations(true) - - if let controller = record.controller as? ViewController, !controller.hasActiveInput { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 1) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - } - self.controllerView.containerView.addSubview(record.controller.view) - record.controller.setIgnoreAppearanceMethodInvocations(false) - - if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) { - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator - - self.controllerView.inTransition = true - navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - strongSelf.navigationTransitionCoordinator = nil - strongSelf.controllerView.inTransition = false - - record.controller.viewDidAppear(true) - - previousController.setIgnoreAppearanceMethodInvocations(true) - previousController.view.removeFromSuperview() - previousController.setIgnoreAppearanceMethodInvocations(false) - previousController.viewDidDisappear(true) - } - }) - } else { - if let index = self._viewControllers.firstIndex(where: { $0.controller === previousController }) { - self._viewControllers[index].transition = .appearance - } - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.containerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator - - self.controllerView.inTransition = true - navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - if let index = strongSelf._viewControllers.firstIndex(where: { $0.controller === previousController }) { - strongSelf._viewControllers[index].transition = .none - } - strongSelf.navigationTransitionCoordinator = nil - strongSelf.controllerView.inTransition = false - - record.controller.viewDidAppear(true) - - previousController.setIgnoreAppearanceMethodInvocations(true) - previousController.view.removeFromSuperview() - previousController.setIgnoreAppearanceMethodInvocations(false) - previousController.viewDidDisappear(true) - } - }) - } - } else { - previousController.viewWillDisappear(false) - previousController.view.removeFromSuperview() - previousController.viewDidDisappear(false) - } - } - - if !animatedAppearingDetailController, let record = appearingDetailController { - record.controller.viewWillAppear(false) - record.controller.setIgnoreAppearanceMethodInvocations(true) - self.controllerView.containerView.addSubview(record.controller.view) - record.controller.setIgnoreAppearanceMethodInvocations(false) - record.controller.viewDidAppear(false) - if let controller = record.controller as? ViewController { - controller.displayNode.recursivelyEnsureDisplaySynchronously(true) - } - } - - if !animatedAppearingMasterController, let record = appearingMasterController, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { - record.controller.viewWillAppear(false) - record.controller.setIgnoreAppearanceMethodInvocations(true) - self.controllerView.masterContainerView.addSubview(record.controller.view) - record.controller.setIgnoreAppearanceMethodInvocations(false) - record.controller.viewDidAppear(false) - if let controller = record.controller as? ViewController { - controller.displayNode.recursivelyEnsureDisplaySynchronously(true) - } - - record.controller.view.frame = firstControllerFrameAndLayout.0 - record.controller.viewDidAppear(transition.isAnimated) - transition.animatePositionAdditive(layer: record.controller.view.layer, offset: CGPoint(x: -firstControllerFrameAndLayout.0.width, y: 0.0)) - } - - for record in self._viewControllers { - let controller = record.controller - if case .none = record.transition, !controllersAndFrames.contains(where: { $0.1.controller === controller }) { - if controller === self.previouslyLaidOutMasterController { - controller.viewWillDisappear(true) - record.transition = .appearance - transition.animatePositionAdditive(layer: controller.view.layer, offset: CGPoint(), to: CGPoint(x: -controller.view.bounds.size.width, y: 0.0), removeOnCompletion: false, completion: { [weak self] in - if let strongSelf = self { - controller.setIgnoreAppearanceMethodInvocations(true) - controller.view.removeFromSuperview() - controller.setIgnoreAppearanceMethodInvocations(false) - controller.viewDidDisappear(true) - controller.view.layer.removeAllAnimations() - for r in strongSelf._viewControllers { - if r.controller === controller { - r.transition = .none - } - } - } - }) - } else { - if controller.isViewLoaded && controller.view.superview != nil { - var isPartOfTransition = false - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - if navigationTransitionCoordinator.topView == controller.view || navigationTransitionCoordinator.bottomView == controller.view { - isPartOfTransition = true - } - } - - if !isPartOfTransition { - controller.viewWillDisappear(false) - controller.setIgnoreAppearanceMethodInvocations(true) - controller.view.removeFromSuperview() - controller.setIgnoreAppearanceMethodInvocations(false) - controller.viewDidDisappear(false) - } - } - } - } - } - - for previous in previousControllers { - var isFound = false - inner: for current in self._viewControllers { - if previous.controller === current.controller { - isFound = true - break inner - } - } - if !isFound { - (previous.controller as? ViewController)?.navigationStackConfigurationUpdated(next: []) - } - } - - (self.view as! NavigationControllerView).topControllerNode = (self._viewControllers.last?.controller as? ViewController)?.displayNode - - for i in 0 ..< self._viewControllers.count { - var currentNext: UIViewController? = (i == (self._viewControllers.count - 1)) ? nil : self._viewControllers[i + 1].controller - if case .single = layoutConfiguration { - currentNext = nil - } - - var previousNext: UIViewController? - inner: for j in 0 ..< previousControllers.count { - if previousControllers[j].controller === self._viewControllers[i].controller { - previousNext = (j == (previousControllers.count - 1)) ? nil : previousControllers[j + 1].controller - break inner - } - } - - if currentNext !== previousNext { - let next = currentNext as? ViewController - (self._viewControllers[i].controller as? ViewController)?.navigationStackConfigurationUpdated(next: next == nil ? [] : [next!]) - } - } - - self.previouslyLaidOutMasterController = masterController - self.previouslyLaidOutTopController = self._viewControllers.last?.controller - } - - public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - if !self.isViewLoaded { - self.loadView() - } - self.validLayout = layout - transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) - - self.updateControllerLayouts(previousControllers: self._viewControllers, layout: layout, transition: transition) - - if let presentedViewController = self.presentedViewController { - let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) - - if let presentedViewController = presentedViewController as? ContainableController { - presentedViewController.containerLayoutUpdated(containedLayout, transition: transition) - } else { - transition.updateFrame(view: presentedViewController.view, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) - } - } - - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - navigationTransitionCoordinator.updateProgress(transition: transition) - } - } - - private var modalTransition: CGFloat = 0.0 - - public func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) { - if self.modalTransition == value { - return - } - let scale = (self.view.bounds.width - 20.0 * 2.0) / self.view.bounds.width - let cornerRadius = value * 10.0 / scale - switch transition { - case let .animated(duration, curve): - let previous = self.displayNode.layer.cornerRadius - self.displayNode.layer.cornerRadius = cornerRadius - if !cornerRadius.isZero { - self.displayNode.clipsToBounds = true - } - self.displayNode.layer.animate(from: previous as NSNumber, to: cornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, completion: { [weak self] _ in - if cornerRadius.isZero { - self?.displayNode.clipsToBounds = false - } - }) - case .immediate: - self.displayNode.layer.cornerRadius = cornerRadius - self.displayNode.clipsToBounds = !cornerRadius.isZero - } - self.modalTransition = value - } - - public func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) { - for record in self._viewControllers { - if let controller = record.controller as? ContainableController { - controller.updateToInterfaceOrientation(orientation) - } - } - } - - open override func loadView() { - self._displayNode = ASDisplayNode(viewBlock: { - return NavigationControllerView() - }, didLoad: nil) - - self.view = self.displayNode.view - self.view.clipsToBounds = true - self.view.autoresizingMask = [] - - self.controllerView.backgroundColor = self.theme.emptyAreaColor - self.controllerView.separatorView.backgroundColor = theme.navigationBar.separatorColor - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.navigationBar.prefersLargeTitles = false - } - self.navigationBar.removeFromSuperview() - - let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) - panRecognizer.delegate = self - panRecognizer.delaysTouchesBegan = false - panRecognizer.cancelsTouchesInView = true - self.view.addGestureRecognizer(panRecognizer) - - if self.topViewController != nil { - self.topViewController?.view.frame = CGRect(origin: CGPoint(), size: self.view.frame.size) - } - } - - @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { - switch recognizer.state { - case .began: - guard let layout = self.validLayout else { - return - } - var beginMasterGesture = false - var beginDetailGesture = false - - let masterControllers: [ControllerRecord] - let detailControllers: [ControllerRecord] - - switch self.layoutConfiguration(for: layout) { - case .masterDetail: - masterControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController { - if case .master = controller.navigationPresentation { - return true - } else { - return false - } - } else { - return false - } - }) - detailControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController { - if case .master = controller.navigationPresentation { - return false - } else { - return true - } - } else { - return true - } - }) - - let locationInMaster = recognizer.location(in: self.controllerView.masterContainerView) - let locationInDetail = recognizer.location(in: self.controllerView.containerView) - - if self.controllerView.masterContainerView.bounds.contains(locationInMaster) { - beginMasterGesture = masterControllers.count >= 2 - } - if self.controllerView.containerView.bounds.contains(locationInDetail) { - beginDetailGesture = detailControllers.count >= 2 - } - case .single: - beginDetailGesture = self._viewControllers.count >= 2 - masterControllers = [] - detailControllers = self._viewControllers - } - - if beginMasterGesture { - guard self.masterTransitionCoordinator == nil else { - return - } - - let topController = masterControllers[masterControllers.count - 1].controller - let bottomController = masterControllers[masterControllers.count - 2].controller - - if let topController = topController as? ViewController { - if !topController.attemptNavigation({ [weak self, weak topController] in - if let topController = topController { - self?.filterController(topController, animated: true) - } - }) { - return - } - } - - topController.viewWillDisappear(true) - let topView = topController.view! - if let bottomController = bottomController as? ViewController { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil) - bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate) - } - bottomController.viewWillAppear(true) - let bottomView = bottomController.view! - - let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.masterContainerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar, didUpdateProgress: { [weak self, weak topController, weak bottomController] progress, transition in - if let strongSelf = self, let topController = topController, let bottomController = bottomController { - (bottomController as? ViewController)?.updateNavigationCustomData((topController as? ViewController)?.customData, progress: 1.0 - progress, transition: transition) - } - }) - if let bottomController = bottomController as? ViewController { - bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true) - } - self.masterTransitionCoordinator = masterTransitionCoordinator - } - if beginDetailGesture { - guard self.navigationTransitionCoordinator == nil else { - return - } - let topController = detailControllers[detailControllers.count - 1].controller - let bottomController = detailControllers[detailControllers.count - 2].controller - - if let topController = topController as? ViewController { - if !topController.attemptNavigation({ [weak self] in - let _ = self?.popViewController(animated: true) - }) { - return - } - } - - topController.viewWillDisappear(true) - let topView = topController.view! - if let bottomController = bottomController as? ViewController { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 1) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil) - bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate) - } - bottomController.viewWillAppear(true) - let bottomView = bottomController.view! - - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar, didUpdateProgress: { [weak self, weak topController, weak bottomController] progress, transition in - if let strongSelf = self, let topController = topController, let bottomController = bottomController { - (bottomController as? ViewController)?.updateNavigationCustomData((topController as? ViewController)?.customData, progress: 1.0 - progress, transition: transition) - } - }) - if let bottomController = bottomController as? ViewController { - bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true) - } - self.navigationTransitionCoordinator = navigationTransitionCoordinator - } - case .changed: - if let layout = self.validLayout { - if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0) - let translation = recognizer.translation(in: self.view).x - let progress = max(0.0, min(1.0, translation / controllerLayout.size.width)) - masterTransitionCoordinator.progress = progress - } - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 1) - let translation = recognizer.translation(in: self.view).x - let progress = max(0.0, min(1.0, translation / controllerLayout.size.width)) - navigationTransitionCoordinator.progress = progress - } - } - case .ended: - if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion { - let velocity = recognizer.velocity(in: self.view).x - - let masterControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { - return true - } - return false - }) - - if velocity > 1000 || masterTransitionCoordinator.progress > 0.2 { - (self.view as! NavigationControllerView).inTransition = true - masterTransitionCoordinator.animateCompletion(velocity, completion: { - (self.view as! NavigationControllerView).inTransition = false - self.masterTransitionCoordinator = nil - - let masterControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { - return true - } - return false - }) - - if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil { - let topController = masterControllers[masterControllers.count - 1].controller - let bottomController = masterControllers[masterControllers.count - 2].controller - - topController.setIgnoreAppearanceMethodInvocations(true) - bottomController.setIgnoreAppearanceMethodInvocations(true) - if let topController = topController as? ViewController { - self.filterController(topController, animated: false) - } - topController.setIgnoreAppearanceMethodInvocations(false) - bottomController.setIgnoreAppearanceMethodInvocations(false) - - topController.viewDidDisappear(true) - bottomController.viewDidAppear(true) - } - }) - } else { - if masterControllers.count >= 2 && masterControllers == nil { - let topController = masterControllers[masterControllers.count - 1].controller - let bottomController = masterControllers[masterControllers.count - 2].controller - - topController.viewWillAppear(true) - bottomController.viewWillDisappear(true) - } - - (self.view as! NavigationControllerView).inTransition = true - masterTransitionCoordinator.animateCancel({ - (self.view as! NavigationControllerView).inTransition = false - self.masterTransitionCoordinator = nil - - let masterControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { - return true - } - return false - }) - - if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil { - let topController = masterControllers[masterControllers.count - 1].controller - let bottomController = masterControllers[masterControllers.count - 2].controller - - topController.viewDidAppear(true) - bottomController.viewDidDisappear(true) - } - }) - } - } - if let layout = self.validLayout, let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { - let velocity = recognizer.velocity(in: self.view).x - - let detailControllers: [ControllerRecord] - switch self.layoutConfiguration(for: layout) { - case .masterDetail: - detailControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController { - if case .master = controller.navigationPresentation { - return false - } else { - return true - } - } else { - return true - } - }) - case .single: - detailControllers = self._viewControllers - } - - if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCompletion(velocity, completion: { - (self.view as! NavigationControllerView).inTransition = false - - let detailControllers: [ControllerRecord] - switch self.layoutConfiguration(for: layout) { - case .masterDetail: - detailControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController { - if case .master = controller.navigationPresentation { - return false - } else { - return true - } - } else { - return true - } - }) - case .single: - detailControllers = self._viewControllers - } - - self.navigationTransitionCoordinator = nil - - if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = detailControllers[detailControllers.count - 1].controller - let bottomController = detailControllers[detailControllers.count - 2].controller - - topController.setIgnoreAppearanceMethodInvocations(true) - bottomController.setIgnoreAppearanceMethodInvocations(true) - self.filterController(topController as! ViewController, animated: false) - topController.setIgnoreAppearanceMethodInvocations(false) - bottomController.setIgnoreAppearanceMethodInvocations(false) - - topController.viewDidDisappear(true) - bottomController.viewDidAppear(true) - } - }) - } else { - if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = detailControllers[detailControllers.count - 1].controller - let bottomController = detailControllers[detailControllers.count - 2].controller - - topController.viewWillAppear(true) - bottomController.viewWillDisappear(true) - } - - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCancel({ - (self.view as! NavigationControllerView).inTransition = false - self.navigationTransitionCoordinator = nil - - let detailControllers: [ControllerRecord] - switch self.layoutConfiguration(for: layout) { - case .masterDetail: - detailControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController { - if case .master = controller.navigationPresentation { - return false - } else { - return true - } - } else { - return true - } - }) - case .single: - detailControllers = self._viewControllers - } - - if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = detailControllers[detailControllers.count - 1].controller - let bottomController = detailControllers[detailControllers.count - 2].controller - - topController.viewDidAppear(true) - bottomController.viewDidDisappear(true) - } - }) - } - } - case .cancelled: - if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion { - let masterControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { - return true - } - return false - }) - - if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil { - let topController = masterControllers[masterControllers.count - 1].controller - let bottomController = masterControllers[masterControllers.count - 2].controller - - topController.viewWillAppear(true) - bottomController.viewWillDisappear(true) - } - - (self.view as! NavigationControllerView).inTransition = true - masterTransitionCoordinator.animateCancel({ - (self.view as! NavigationControllerView).inTransition = false - self.masterTransitionCoordinator = nil - - let masterControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation { - return true - } - return false - }) - - if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil { - let topController = masterControllers[masterControllers.count - 1].controller - let bottomController = masterControllers[masterControllers.count - 2].controller - - topController.viewDidAppear(true) - bottomController.viewDidDisappear(true) - } - }) - } - if let layout = self.validLayout, let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { - let detailControllers: [ControllerRecord] - switch self.layoutConfiguration(for: layout) { - case .masterDetail: - detailControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController { - if case .master = controller.navigationPresentation { - return false - } else { - return true - } - } else { - return true - } - }) - case .single: - detailControllers = self._viewControllers - } - - if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = detailControllers[detailControllers.count - 1].controller - let bottomController = detailControllers[detailControllers.count - 2].controller - - topController.viewWillAppear(true) - bottomController.viewWillDisappear(true) - } - - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCancel({ - (self.view as! NavigationControllerView).inTransition = false - self.navigationTransitionCoordinator = nil - - let detailControllers: [ControllerRecord] - switch self.layoutConfiguration(for: layout) { - case .masterDetail: - detailControllers = self._viewControllers.filter({ record in - if let controller = record.controller as? ViewController { - if case .master = controller.navigationPresentation { - return false - } else { - return true - } - } else { - return true - } - }) - case .single: - detailControllers = self._viewControllers - } - - if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil { - let topController = detailControllers[detailControllers.count - 1].controller - let bottomController = detailControllers[detailControllers.count - 2].controller - - topController.viewDidAppear(true) - bottomController.viewDidDisappear(true) - } - }) - } - default: - break - } - } - - public func pushViewController(_ controller: ViewController) { - self.pushViewController(controller, completion: {}) - } - - public func pushViewController(_ controller: ViewController, animated: Bool = true, completion: @escaping () -> Void) { - let navigateAction: () -> Void = { [weak self] in - guard let strongSelf = self else { - return - } - - if !controller.hasActiveInput { - strongSelf.view.endEditing(true) - } - strongSelf.scheduleAfterLayout({ - guard let strongSelf = self else { - return - } - - if let validLayout = strongSelf.validLayout { - let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - strongSelf.currentPushDisposable.set((controller.ready.get() - |> deliverOnMainQueue - |> take(1)).start(next: { _ in - guard let strongSelf = self else { - return - } - - if let validLayout = strongSelf.validLayout { - let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) - - let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - if containerLayout != appliedLayout { - controller.containerLayoutUpdated(containerLayout, transition: .immediate) - } - strongSelf.pushViewController(controller, animated: animated) - completion() - } - })) - } else { - strongSelf.pushViewController(controller, animated: false) - completion() - } - }) - } - - if let lastController = self.viewControllers.last as? ViewController, !lastController.attemptNavigation(navigateAction) { - } else { - navigateAction() - } - } - - open override func pushViewController(_ viewController: UIViewController, animated: Bool) { - self.currentPushDisposable.set(nil) - - var controllers = self.viewControllers - if let controller = viewController as? ViewController { - switch controller.navigationPresentation { - case .default: - controllers.append(viewController) - case .master: - var i = 0 - loop: while i < controllers.count { - if let currentController = controllers[i] as? ViewController { - switch currentController.navigationPresentation { - case .master: - break - case .default: - break loop - } - } else { - break loop - } - i += 1 - } - controllers.insert(viewController, at: i) - } - } else { - controllers.append(viewController) - } - self.setViewControllers(controllers, animated: animated) - } - - public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { - self.view.endEditing(true) - if !controller.hasActiveInput { - self.view.endEditing(true) - } - if let validLayout = self.validLayout { - var (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) - controllerLayout.inputHeight = nil - controller.containerLayoutUpdated(controllerLayout, transition: .immediate) - } - self.currentPushDisposable.set((controller.ready.get() - |> deliverOnMainQueue - |> take(1)).start(next: { [weak self] _ in - if let strongSelf = self { - ready?.set(true) - var controllers = strongSelf.viewControllers - controllers.removeLast() - controllers.append(controller) - strongSelf.setViewControllers(controllers, animated: animated) - } - })) - } - - public func filterController(_ controller: ViewController, animated: Bool) { - let controllers = self.viewControllers.filter({ $0 !== controller }) - if controllers.count != self.viewControllers.count { - self.setViewControllers(controllers, animated: animated) - } - } - - public func replaceControllersAndPush(controllers: [UIViewController], controller: ViewController, animated: Bool, options: NavigationAnimationOptions = [], ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { - self.view.endEditing(true) - var animated = animated - - self.scheduleAfterLayout { [weak self] in - guard let strongSelf = self else { - return - } - if let validLayout = strongSelf.validLayout { - let configuration = strongSelf.layoutConfiguration(for: validLayout) - var (_, controllerLayout) = strongSelf.layoutDataForConfiguration(configuration, layout: validLayout, index: strongSelf.viewControllers.count) - controllerLayout.inputHeight = nil - if options.contains(.removeOnMasterDetails) { - switch configuration { - case .masterDetail: - animated = false - default: - break - } - } - controller.containerLayoutUpdated(controllerLayout, transition: .immediate) - } - strongSelf.currentPushDisposable.set((controller.ready.get() - |> deliverOnMainQueue - |> take(1)).start(next: { _ in - guard let strongSelf = self else { - return - } - ready?.set(true) - var controllers = controllers - controllers.append(controller) - strongSelf.setViewControllers(controllers, animated: animated) - completion() - })) - } - } - - public func replaceAllButRootController(_ controller: ViewController, animated: Bool, animationOptions: NavigationAnimationOptions = [], ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { - self.view.endEditing(true) - self.scheduleAfterLayout { [weak self] in - guard let strongSelf = self else { - return - } - var animated = animated - if let validLayout = strongSelf.validLayout { - let configuration = strongSelf.layoutConfiguration(for: validLayout) - var (_, controllerLayout) = strongSelf.layoutDataForConfiguration(configuration, layout: validLayout, index: strongSelf.viewControllers.count) - controllerLayout.inputHeight = nil - controller.containerLayoutUpdated(controllerLayout, transition: .immediate) - switch configuration { - case .masterDetail: - if animationOptions.contains(.removeOnMasterDetails) { - animated = false - } - case .single: - break - } - } - strongSelf.currentPushDisposable.set((controller.ready.get() - |> deliverOnMainQueue - |> take(1)).start(next: { _ in - guard let strongSelf = self else { - return - } - ready?.set(true) - var controllers = strongSelf.viewControllers - while controllers.count > 1 { - controllers.removeLast() - } - controllers.append(controller) - strongSelf.setViewControllers(controllers, animated: animated) - completion() - })) - } - } - - public func popToRoot(animated: Bool) { - var controllers = self.viewControllers - while controllers.count > 1 { - controllers.removeLast() - } - self.setViewControllers(controllers, animated: animated) - } - - override open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { - var poppedControllers: [UIViewController] = [] - var found = false - var controllers = self.viewControllers - if !controllers.contains(where: { $0 === viewController }) { - return nil - } - while !controllers.isEmpty { - if controllers[controllers.count - 1] === viewController { - found = true - break - } - poppedControllers.insert(controllers[controllers.count - 1], at: 0) - controllers.removeLast() - } - if found { - self.setViewControllers(controllers, animated: animated) - return poppedControllers - } else { - return nil - } - } - - open override func popViewController(animated: Bool) -> UIViewController? { - var controller: UIViewController? - var controllers = self.viewControllers - if controllers.count != 0 { - controller = controllers[controllers.count - 1] as UIViewController - controllers.remove(at: controllers.count - 1) - self.setViewControllers(controllers, animated: animated) - } - return controller - } - - open override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { - var resultControllers: [ControllerRecord] = [] - for controller in viewControllers { - var found = false - inner: for current in self._viewControllers { - if current.controller === controller { - resultControllers.append(current) - found = true - break inner - } - } - if !found { - resultControllers.append(ControllerRecord(controller: controller)) - } - } - let previousControllers = self._viewControllers - self._viewControllers = resultControllers - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - navigationTransitionCoordinator.complete() - } - if let layout = self.validLayout { - self.updateControllerLayouts(previousControllers: previousControllers, layout: layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) - } - } - - override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { - if let controller = viewControllerToPresent as? NavigationController { - controller.navigation_setDismiss({ [weak self] in - if let strongSelf = self { - strongSelf.dismiss(animated: false, completion: nil) - } - }, rootController: self.view!.window!.rootViewController) - self._presentedViewController = controller - - self.view.endEditing(true) - if let validLayout = self.validLayout { - controller.containerLayoutUpdated(validLayout, transition: .immediate) - } - - var ready: Signal = .single(true) - - if let controller = controller.topViewController as? ViewController { - ready = controller.ready.get() - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue - } - - self.currentPresentDisposable.set(ready.start(next: { [weak self] _ in - if let strongSelf = self { - if flag { - controller.view.frame = strongSelf.view.bounds.offsetBy(dx: 0.0, dy: strongSelf.view.bounds.height) - strongSelf.view.addSubview(controller.view) - UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: { - controller.view.frame = strongSelf.view.bounds - }, completion: { _ in - if let completion = completion { - completion() - } - }) - } else { - controller.view.frame = strongSelf.view.bounds - strongSelf.view.addSubview(controller.view) - - if let completion = completion { - completion() - } - } - } - })) - } else { - preconditionFailure("NavigationController can't present \(viewControllerToPresent). Only subclasses of NavigationController are allowed.") - } - } - - override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { - if let controller = self.presentedViewController { - if flag { - UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: { - controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) - }, completion: { _ in - controller.view.removeFromSuperview() - self._presentedViewController = nil - if let completion = completion { - completion() - } - }) - } else { - controller.view.removeFromSuperview() - self._presentedViewController = nil - if let completion = completion { - completion() - } - } - } - } - - public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return false - } - - public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { - return true - } - return false - } - - public final var currentWindow: WindowHost? { - if let window = self.view.window as? WindowHost { - return window - } else if let superwindow = self.view.window { - for subview in superwindow.subviews { - if let subview = subview as? WindowHost { - return subview - } - } - } - return nil - } - - private func scheduleAfterLayout(_ f: @escaping () -> Void) { - (self.view as? UITracingLayerView)?.schedule(layout: { - f() - }) - self.view.setNeedsLayout() - } - - private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) { - let requestId = self.scheduledLayoutTransitionRequestId - self.scheduledLayoutTransitionRequestId += 1 - self.scheduledLayoutTransitionRequest = (requestId, transition) - (self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in - if let strongSelf = self { - if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId { - strongSelf.scheduledLayoutTransitionRequest = nil - strongSelf.requestLayout(transition: currentRequestTransition) - } - } - }) - self.view.setNeedsLayout() - } - - private func requestLayout(transition: ContainedViewLayoutTransition) { - if self.isViewLoaded, let validLayout = self.validLayout { - self.containerLayoutUpdated(validLayout, transition: transition) - } - } -} diff --git a/submodules/Display/Display/ScrollToTopProxyView.swift b/submodules/Display/Display/ScrollToTopProxyView.swift index 1544e0d027..3e6f76a24a 100644 --- a/submodules/Display/Display/ScrollToTopProxyView.swift +++ b/submodules/Display/Display/ScrollToTopProxyView.swift @@ -6,6 +6,8 @@ class ScrollToTopView: UIScrollView, UIScrollViewDelegate { override init(frame: CGRect) { super.init(frame: frame) + self.isOpaque = false + self.backgroundColor = .clear self.delegate = self self.scrollsToTop = true if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { diff --git a/submodules/Display/Display/StatusBarHost.swift b/submodules/Display/Display/StatusBarHost.swift index 35a7299d4e..58f24fcb20 100644 --- a/submodules/Display/Display/StatusBarHost.swift +++ b/submodules/Display/Display/StatusBarHost.swift @@ -11,4 +11,6 @@ public protocol StatusBarHost { var keyboardView: UIView? { get } var handleVolumeControl: Signal { get } + + func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool) } diff --git a/submodules/Display/Display/StatusBarManager.swift b/submodules/Display/Display/StatusBarManager.swift index b051951bfd..16bd08b8f0 100644 --- a/submodules/Display/Display/StatusBarManager.swift +++ b/submodules/Display/Display/StatusBarManager.swift @@ -278,7 +278,7 @@ class StatusBarManager { } self.volumeControlStatusBarNode.isDark = isDark - if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows { + /*if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows { let statusBarStyle: UIStatusBarStyle if forceInCallStatusBarText != nil { statusBarStyle = .lightContent @@ -298,6 +298,6 @@ class StatusBarManager { } } else { statusBarView.alpha = 0.0 - } + }*/ } } diff --git a/submodules/Display/Display/ViewController.swift b/submodules/Display/Display/ViewController.swift index c3764f6e03..ad4b6210f4 100644 --- a/submodules/Display/Display/ViewController.swift +++ b/submodules/Display/Display/ViewController.swift @@ -58,6 +58,8 @@ open class ViewControllerPresentationArguments { public enum ViewControllerNavigationPresentation { case `default` case master + case modal + case modalInLargeLayout } @objc open class ViewController: UIViewController, ContainableController { @@ -446,9 +448,9 @@ public enum ViewControllerNavigationPresentation { override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if let navigationController = self.navigationController as? NavigationController { - navigationController.dismiss(animated: flag, completion: completion) + navigationController.filterController(self, animated: flag) } else { - super.dismiss(animated: flag, completion: completion) + assertionFailure() } } @@ -468,10 +470,10 @@ public enum ViewControllerNavigationPresentation { public func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { controller.presentationArguments = arguments switch context { - case .current: - self.presentationContext.present(controller, on: PresentationSurfaceLevel(rawValue: 0), completion: completion) - case let .window(level): - self.window?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) + case .current: + self.presentationContext.present(controller, on: PresentationSurfaceLevel(rawValue: 0), completion: completion) + case let .window(level): + self.window?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) } } @@ -507,6 +509,7 @@ public enum ViewControllerNavigationPresentation { } open func dismiss(completion: (() -> Void)? = nil) { + (self.navigationController as? NavigationController)?.filterController(self, animated: true) } @available(iOSApplicationExtension 9.0, iOS 9.0, *) diff --git a/submodules/Display/Display/WindowContent.swift b/submodules/Display/Display/WindowContent.swift index c8123d4b00..ac1014361b 100644 --- a/submodules/Display/Display/WindowContent.swift +++ b/submodules/Display/Display/WindowContent.swift @@ -692,6 +692,10 @@ public class Window1 { self._rootController = value if let rootController = self._rootController { + if let rootController = rootController as? NavigationController { + rootController.statusBarHost = self.statusBarHost + rootController.keyboardManager = self.keyboardManager + } if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero { rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate) } @@ -803,7 +807,7 @@ public class Window1 { } } } - keyboardManager.surfaces = keyboardSurfaces + //keyboardManager.surfaces = keyboardSurfaces var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) let orientationToLock: UIInterfaceOrientationMask diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 0568bf986f..eeeddc64b3 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -62,7 +62,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { var currentAccessibilityAreas: [AccessibilityAreaNode] = [] - private var previousContentOffset: CGPoint? private var isDeceleratingBecauseOfDragging = false private let hiddenMediaDisposable = MetaDisposable() @@ -372,7 +371,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.scrollNode.view.contentOffset = contentOffset if didSetScrollOffset { - self.previousContentOffset = contentOffset self.updateNavigationBar() if self.currentLayout != nil { self.setupScrollOffsetOnLayout = false @@ -660,8 +658,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) - self.updateNavigationBar() - self.previousContentOffset = self.scrollNode.view.contentOffset } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { @@ -694,50 +690,14 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { minBarHeight = 20.0 } - var pageProgress: CGFloat = 0.0 - if !self.scrollNode.view.contentSize.height.isZero { - let value = (contentOffset.y + self.scrollNode.view.contentInset.top) / (self.scrollNode.view.contentSize.height - bounds.size.height + self.scrollNode.view.contentInset.top) - pageProgress = max(0.0, min(1.0, value)) - } - - let delta: CGFloat - if self.setupScrollOffsetOnLayout { - delta = 0.0 - } else if let previousContentOffset = self.previousContentOffset { - delta = contentOffset.y - previousContentOffset.y - } else { - delta = 0.0 - } - self.previousContentOffset = contentOffset - var transition: ContainedViewLayoutTransition = .immediate var navigationBarFrame = self.navigationBar.frame navigationBarFrame.size.width = bounds.size.width if navigationBarFrame.size.height.isZero { navigationBarFrame.size.height = maxBarHeight } - if forceState { - transition = .animated(duration: 0.3, curve: .spring) - - let transitionFactor = (navigationBarFrame.size.height - minBarHeight) / (maxBarHeight - minBarHeight) - - if contentOffset.y <= -self.scrollNode.view.contentInset.top || transitionFactor > 0.4 { - navigationBarFrame.size.height = maxBarHeight - } else { - navigationBarFrame.size.height = minBarHeight - } - } else { - if contentOffset.y <= -self.scrollNode.view.contentInset.top { - navigationBarFrame.size.height = maxBarHeight - } else { - navigationBarFrame.size.height -= delta - } - navigationBarFrame.size.height = max(minBarHeight, min(maxBarHeight, navigationBarFrame.size.height)) - } - if self.setupScrollOffsetOnLayout { - navigationBarFrame.size.height = maxBarHeight - } + navigationBarFrame.size.height = maxBarHeight let transitionFactor = (navigationBarFrame.size.height - minBarHeight) / (maxBarHeight - minBarHeight) @@ -756,7 +716,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } transition.updateFrame(node: self.navigationBar, frame: navigationBarFrame) - self.navigationBar.updateLayout(size: navigationBarFrame.size, minHeight: minBarHeight, maxHeight: maxBarHeight, topInset: containerLayout.safeInsets.top, leftInset: containerLayout.safeInsets.left, rightInset: containerLayout.safeInsets.right, title: title, pageProgress: pageProgress, transition: transition) + self.navigationBar.updateLayout(size: navigationBarFrame.size, minHeight: minBarHeight, maxHeight: maxBarHeight, topInset: containerLayout.safeInsets.top, leftInset: containerLayout.safeInsets.left, rightInset: containerLayout.safeInsets.right, title: title, pageProgress: 0.0, transition: transition) transition.animateView { self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: navigationBarFrame.size.height, left: 0.0, bottom: containerLayout.intrinsicInsets.bottom, right: 0.0) diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index c338291667..4ff10d1a3f 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -505,14 +505,6 @@ open class ItemListController: ViewController, KeyShor self.didDisappear?(animated) } - override open func dismiss(completion: (() -> Void)? = nil) { - if !self.isDismissed { - self.isDismissed = true - (self.displayNode as! ItemListControllerNode).animateOut(completion: completion) - self.updateTransitionWhenPresentedAsModal?(0.0, .animated(duration: 0.2, curve: .easeInOut)) - } - } - public func frameForItemNode(_ predicate: (ListViewItemNode) -> Bool) -> CGRect? { var result: CGRect? (self.displayNode as! ItemListControllerNode).listNode.forEachItemNode { itemNode in diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index 50f9d64cce..8868e0b5fe 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -508,7 +508,7 @@ open class LegacyController: ViewController, PresentableController { case .custom: self.presentingViewController?.dismiss(animated: false, completion: completion) case .navigation: - break + (self.navigationController as? NavigationController)?.filterController(self, animated: true) } } diff --git a/submodules/LocationUI/Sources/LegacyLocationPicker.swift b/submodules/LocationUI/Sources/LegacyLocationPicker.swift index bdb9575906..46b8d573aa 100644 --- a/submodules/LocationUI/Sources/LegacyLocationPicker.swift +++ b/submodules/LocationUI/Sources/LegacyLocationPicker.swift @@ -15,7 +15,8 @@ private func generateClearIcon(color: UIColor) -> UIImage? { } public func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, peer: Peer, sendLocation: @escaping (CLLocationCoordinate2D, MapVenue?, String?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, theme: PresentationTheme, customLocationPicker: Bool = false, hasLiveLocation: Bool = true, presentationCompleted: @escaping () -> Void = {}) -> ViewController { - let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: theme) + let legacyController = LegacyController(presentation: .navigation, theme: theme) + legacyController.navigationPresentation = .modal legacyController.presentationCompleted = { presentationCompleted() } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index 2cf98a5b2b..ab55a0c503 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -1105,6 +1105,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi } let controller = ItemListController(context: context, state: signal) + controller.navigationPresentation = .modal controller.experimentalSnapScrollToItem = true dismissImpl = { [weak controller] in controller?.view.endEditing(true) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index ac1599aa2f..5f6c4575d5 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -600,20 +600,20 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa } } } - presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in - }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in + }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) }) dismissController = { [weak controller] in controller?.dismiss() } - presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushControllerImpl?(controller) return current } }) }, openAdmin: { participant in - presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in - }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in + }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) }) let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) @@ -734,14 +734,14 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa } }, openPeer: { _, participant in if let participant = participant?.participant, case .member = participant { - presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in + pushControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in updateState { state in return state.withUpdatedSearchingMembers(false) } - }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) } - }, present: { c, a in - presentControllerImpl?(c, a) + }, pushController: { c in + pushControllerImpl?(c) }) } diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift index 51804ad3cb..0d5b25a228 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift @@ -486,8 +486,8 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId) if let rendered = rendered, case .member = rendered.participant { arguments.openPeer(rendered) } - }, present: { c, a in - presentControllerImpl?(c, a) + }, pushController: { c in + pushControllerImpl?(c) }) } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index f1dbed3542..9393905b6d 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -403,8 +403,8 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) -> presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_AddBotErrorHaveRights, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_AddBotAsAdmin, action: { contactsController?.dismiss() - presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: memberId, initialParticipant: nil, updated: { _ in - }, upgradedToSupergroup: { _, f in f () }, transferedOwnership: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: memberId, initialParticipant: nil, updated: { _ in + }, upgradedToSupergroup: { _, f in f () }, transferedOwnership: { _ in })) })]), nil) } else { presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_AddBotErrorHaveRights, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) @@ -500,8 +500,8 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) -> if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic) { pushControllerImpl?(infoController) } - }, present: { c, a in - presentControllerImpl?(c, a) + }, pushController: { c in + pushControllerImpl?(c) }) } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 7522796352..868c5557bc 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -296,7 +296,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, PresentationDateTimeFormat)> - init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) { self.context = context self.openPeer = openPeer self.mode = mode @@ -341,16 +341,16 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod state.revealedPeerId = nil return state } - present(channelAdminController(context: context, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in - }, upgradedToSupergroup: { _, f in f() }, transferedOwnership: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushController(channelAdminController(context: context, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in + }, upgradedToSupergroup: { _, f in f() }, transferedOwnership: { _ in })) }, restrictPeer: { participant in updateState { state in var state = state state.revealedPeerId = nil return state } - present(channelBannedMemberController(context: context, peerId: peerId, memberId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in - }, upgradedToSupergroup: { _, f in f() }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushController(channelBannedMemberController(context: context, peerId: peerId, memberId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in + }, upgradedToSupergroup: { _, f in f() })) }, removePeer: { memberId in updateState { state in var state = state diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift index d8e0d36666..c6817f423e 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift @@ -47,6 +47,8 @@ final class ChannelMembersSearchController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationPresentation = .modal + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.title = self.presentationData.strings.Channel_Members_Title @@ -83,8 +85,8 @@ final class ChannelMembersSearchController: ViewController { self.controllerNode.requestOpenPeerFromSearch = { [weak self] peer, participant in self?.openPeer(peer, participant) } - self.controllerNode.present = { [weak self] c, a in - self?.present(c, in: .window(.root), with: a) + self.controllerNode.pushController = { [weak self] c in + (self?.navigationController as? NavigationController)?.pushViewController(c) } self.displayNodeDidLoad() @@ -113,14 +115,6 @@ final class ChannelMembersSearchController: ViewController { } } - override func dismiss(completion: (() -> Void)? = nil) { - self.view.endEditing(true) - self.controllerNode.animateOut(completion: { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - completion?() - }) - } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index 4693a61046..3d3a760b68 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -108,7 +108,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { var requestActivateSearch: (() -> Void)? var requestDeactivateSearch: (() -> Void)? var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)? - var present: ((ViewController, Any?) -> Void)? + var pushController: ((ViewController) -> Void)? var presentationData: PresentationData @@ -362,8 +362,8 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { self?.requestOpenPeerFromSearch?(peer, participant) }, updateActivity: { value in - }, present: { [weak self] c, a in - self?.present?(c, a) + }, pushController: { [weak self] c in + self?.pushController?(c) }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index bbc31cf414..49982247a4 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -797,8 +797,8 @@ public func channelPermissionsController(context: AccountContext, peerId origina upgradedToSupergroupImpl?(upgradedPeerId, f) }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } - }, present: { c, a in - presentControllerImpl?(c, a) + }, pushController: { c in + pushControllerImpl?(c) }) } diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 899696b586..0c2c99247c 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -811,6 +811,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device var addToExistingImpl: (() -> Void)? var openChatImpl: ((PeerId) -> Void)? var replaceControllerImpl: ((ViewController) -> Void)? + var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? var openUrlImpl: ((String) -> Void)? var openAddressImpl: ((DeviceContactAddressData) -> Void)? @@ -908,7 +909,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device return state } }, updatePhoneLabel: { id, currentLabel in - presentControllerImpl?(phoneLabelController(context: context, currentLabel: currentLabel, completion: { value in + pushControllerImpl?(phoneLabelController(context: context, currentLabel: currentLabel, completion: { value in updateState { state in var state = state for i in 0 ..< state.phoneNumbers.count { @@ -919,7 +920,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device } return state } - }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + })) }, deletePhone: { id in updateState { state in var state = state @@ -976,14 +977,14 @@ public func deviceContactInfoController(context: AccountContext, subject: Device presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } case .createContact: - presentControllerImpl?(deviceContactInfoController(context: context, subject: .create(peer: subject.peer, contactData: subject.contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + pushControllerImpl?(deviceContactInfoController(context: context, subject: .create(peer: subject.peer, contactData: subject.contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in dismissImpl?(false) if let peer = peer { } else { } - }), completed: nil, cancelled: nil), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }), completed: nil, cancelled: nil)) case .addToExisting: addToExistingImpl?() case .sendMessage: @@ -1210,6 +1211,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device } let controller = DeviceContactInfoController(context: context, state: signal) + controller.navigationPresentation = .modal addToExistingImpl = { [weak controller] in guard let controller = controller else { return @@ -1226,6 +1228,9 @@ public func deviceContactInfoController(context: AccountContext, subject: Device replaceControllerImpl = { [weak controller] value in (controller?.navigationController as? NavigationController)?.replaceTopController(value, animated: true) } + pushControllerImpl = { [weak controller] c in + (controller?.navigationController as? NavigationController)?.pushViewController(c) + } presentControllerImpl = { [weak controller] value, presentationArguments in controller?.present(value, in: .window(.root), with: presentationArguments) } @@ -1293,7 +1298,8 @@ public func deviceContactInfoController(context: AccountContext, subject: Device private func addContactToExisting(context: AccountContext, parentController: ViewController, contactData: DeviceContactExtendedData, completion: @escaping (Peer?, DeviceContactStableId, DeviceContactExtendedData) -> Void) { let contactsController = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: context, title: { $0.Contacts_Title }, displayDeviceContacts: true)) - parentController.present(contactsController, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + contactsController.navigationPresentation = .modal + (parentController.navigationController as? NavigationController)?.pushViewController(contactsController) let _ = (contactsController.result |> deliverOnMainQueue).start(next: { peer in if let peer = peer { diff --git a/submodules/PeerInfoUI/Sources/GroupInfoController.swift b/submodules/PeerInfoUI/Sources/GroupInfoController.swift index 11ba952010..c4a4d4ecf2 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoController.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoController.swift @@ -1868,10 +1868,10 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId: let _ = (peerView.get() |> take(1) |> deliverOnMainQueue).start(next: { peerView in - presentControllerImpl?(channelAdminController(context: context, peerId: peerView.peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in + pushControllerImpl?(channelAdminController(context: context, peerId: peerView.peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in }, upgradedToSupergroup: { upgradedPeerId, f in upgradedToSupergroupImpl?(upgradedPeerId, f) - }, transferedOwnership: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }, transferedOwnership: { _ in })) }) }, restrictPeer: { participant in let _ = (peerView.get() @@ -2257,8 +2257,8 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId: if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic) { arguments.pushController(infoController) } - }, present: { c, a in - presentControllerImpl?(c, a) + }, pushController: { c in + pushControllerImpl?(c) }) } diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift index e546837462..caac5af9c8 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift @@ -14,20 +14,20 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { let searchContext: GroupMembersSearchContext? let cancel: () -> Void let openPeer: (Peer, RenderedChannelParticipant?) -> Void - let present: (ViewController, Any?) -> Void + let pushController: (ViewController) -> Void let searchMode: ChannelMembersSearchMode private var updateActivity: ((Bool) -> Void)? private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(context: AccountContext, peerId: PeerId, searchContext: GroupMembersSearchContext?, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(context: AccountContext, peerId: PeerId, searchContext: GroupMembersSearchContext?, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, pushController: @escaping (ViewController) -> Void) { self.context = context self.peerId = peerId self.searchContext = searchContext self.cancel = cancel self.openPeer = openPeer - self.present = present + self.pushController = pushController self.searchMode = searchMode activityDisposable.set((activity.get() |> mapToSignal { value -> Signal in if value { @@ -73,8 +73,8 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode { return ChannelMembersSearchItemNode(context: self.context, peerId: self.peerId, searchMode: self.searchMode, searchContext: self.searchContext, openPeer: self.openPeer, cancel: self.cancel, updateActivity: { [weak self] value in self?.activity.set(value) - }, present: { [weak self] c, a in - self?.present(c, a) + }, pushController: { [weak self] c in + self?.pushController(c) }) } } @@ -82,10 +82,10 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode { private let containerNode: ChannelMembersSearchContainerNode - init(context: AccountContext, peerId: PeerId, searchMode: ChannelMembersSearchMode, searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(context: AccountContext, peerId: PeerId, searchMode: ChannelMembersSearchMode, searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void) { self.containerNode = ChannelMembersSearchContainerNode(context: context, peerId: peerId, mode: searchMode, filters: [], searchContext: searchContext, openPeer: { peer, participant in openPeer(peer, participant) - }, updateActivity: updateActivity, present: present) + }, updateActivity: updateActivity, pushController: pushController) self.containerNode.cancel = { cancel() } diff --git a/submodules/PeerInfoUI/Sources/PhoneLabelController.swift b/submodules/PeerInfoUI/Sources/PhoneLabelController.swift index 36018f2cc9..93bf2c7df0 100644 --- a/submodules/PeerInfoUI/Sources/PhoneLabelController.swift +++ b/submodules/PeerInfoUI/Sources/PhoneLabelController.swift @@ -126,6 +126,7 @@ public func phoneLabelController(context: AccountContext, currentLabel: String, let controller = ItemListController(context: context, state: signal |> afterDisposed { }) + controller.navigationPresentation = .modal controller.enableInteractiveDismiss = true completeImpl = { [weak controller] in diff --git a/submodules/PeerInfoUI/Sources/UserInfoController.swift b/submodules/PeerInfoUI/Sources/UserInfoController.swift index 5ef6d36001..ed78567d10 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoController.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoController.swift @@ -766,7 +766,7 @@ private func getUserPeer(postbox: Postbox, peerId: PeerId) -> Signal<(Peer?, Cac } } -public func openAddPersonContactImpl(context: AccountContext, peerId: PeerId, present: @escaping (ViewController, Any?) -> Void) { +public func openAddPersonContactImpl(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) { let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId) |> deliverOnMainQueue).start(next: { peer, cachedData in guard let user = peer as? TelegramUser, let contactData = DeviceContactExtendedData(peer: user) else { @@ -778,7 +778,7 @@ public func openAddPersonContactImpl(context: AccountContext, peerId: PeerId, pr shareViaException = peerStatusSettings.contains(.addExceptionWhenAddingContact) } - present(deviceContactInfoController(context: context, subject: .create(peer: user, contactData: contactData, isSharing: true, shareViaException: shareViaException, completion: { peer, stableId, contactData in + pushController(deviceContactInfoController(context: context, subject: .create(peer: user, contactData: contactData, isSharing: true, shareViaException: shareViaException, completion: { peer, stableId, contactData in if let peer = peer as? TelegramUser { if let phone = peer.phone, !phone.isEmpty { } @@ -786,7 +786,7 @@ public func openAddPersonContactImpl(context: AccountContext, peerId: PeerId, pr let presentationData = context.sharedContext.currentPresentationData.with { $0 } present(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(presentationData.strings.AddContact_StatusSuccess(peer.compactDisplayTitle).0, true)), nil) } - }), completed: nil, cancelled: nil), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }), completed: nil, cancelled: nil)) }) } @@ -916,7 +916,9 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe }, openChat: { openChatImpl?() }, addContact: { - openAddPersonContactImpl(context: context, peerId: peerId, present: { c, a in + openAddPersonContactImpl(context: context, peerId: peerId, pushController: { c in + pushControllerImpl?(c) + }, present: { c, a in presentControllerImpl?(c, a) }) }, shareContact: { diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift index 6bfae67839..152fd4b647 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift @@ -315,7 +315,7 @@ public func proxySettingsController(context: AccountContext, mode: ProxySettings } public func proxySettingsController(accountManager: AccountManager, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, theme: PresentationTheme, strings: PresentationStrings, updatedPresentationData: Signal<(theme: PresentationTheme, strings: PresentationStrings), NoError>) -> ViewController { - var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + var pushControllerImpl: ((ViewController) -> Void)? var dismissImpl: (() -> Void)? let stateValue = Atomic(value: ProxySettingsControllerState()) let statePromise = ValuePromise(stateValue.with { $0 }) @@ -342,7 +342,7 @@ public func proxySettingsController(accountManager: AccountManager, postbox: Pos return current }).start() }, addNewServer: { - presentControllerImpl?(proxyServerSettingsController(theme: theme, strings: strings, updatedPresentationData: updatedPresentationData, accountManager: accountManager, postbox: postbox, network: network, currentSettings: nil), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushControllerImpl?(proxyServerSettingsController(theme: theme, strings: strings, updatedPresentationData: updatedPresentationData, accountManager: accountManager, postbox: postbox, network: network, currentSettings: nil)) }, activateServer: { server in let _ = updateProxySettingsInteractively(accountManager: accountManager, { current in var current = current @@ -355,7 +355,7 @@ public func proxySettingsController(accountManager: AccountManager, postbox: Pos return current }).start() }, editServer: { server in - presentControllerImpl?(proxyServerSettingsController(theme: theme, strings: strings, updatedPresentationData: updatedPresentationData, accountManager: accountManager, postbox: postbox, network: network, currentSettings: server), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushControllerImpl?(proxyServerSettingsController(theme: theme, strings: strings, updatedPresentationData: updatedPresentationData, accountManager: accountManager, postbox: postbox, network: network, currentSettings: server)) }, removeServer: { server in let _ = updateProxySettingsInteractively(accountManager: accountManager, { current in var current = current @@ -438,8 +438,9 @@ public func proxySettingsController(accountManager: AccountManager, postbox: Pos } let controller = ItemListController(theme: theme, strings: strings, updatedPresentationData: updatedPresentationData, state: signal, tabBarItem: nil) - presentControllerImpl = { [weak controller] c, a in - controller?.present(c, in: .window(.root), with: a) + controller.navigationPresentation = .modal + pushControllerImpl = { [weak controller] c in + (controller?.navigationController as? NavigationController)?.pushViewController(c) } dismissImpl = { [weak controller] in controller?.dismiss() diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift index 6b2c64fb8f..28d152ec18 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift @@ -369,6 +369,7 @@ func proxyServerSettingsController(theme: PresentationTheme, strings: Presentati } let controller = ItemListController(theme: theme, strings: strings, updatedPresentationData: updatedPresentationData, state: signal, tabBarItem: nil) + controller.navigationPresentation = .modal presentImpl = { [weak controller] c, d in controller?.present(c, in: .window(.root), with: d) } diff --git a/submodules/SettingsUI/Sources/DebugController.swift b/submodules/SettingsUI/Sources/DebugController.swift index 3527bcd4ef..534b578b52 100644 --- a/submodules/SettingsUI/Sources/DebugController.swift +++ b/submodules/SettingsUI/Sources/DebugController.swift @@ -172,7 +172,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { let _ = enqueueMessages(account: context.account, peerId: peerId, messages: messages).start() } } - arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) + arguments.pushController(controller) })) } items.append(ActionSheetButtonItem(title: "Via Email", color: .accent, action: { [weak actionSheet] in @@ -225,7 +225,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { let _ = enqueueMessages(account: context.account, peerId: peerId, messages: messages).start() } } - arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) + arguments.pushController(controller) })) } @@ -272,7 +272,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { let _ = enqueueMessages(account: context.account, peerId: peerId, messages: messages).start() } } - arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) + arguments.pushController(controller) }) }) case let .sendCriticalLogs(theme): @@ -301,7 +301,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { let _ = enqueueMessages(account: context.account, peerId: peerId, messages: messages).start() } } - arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) + arguments.pushController(controller) })) } diff --git a/submodules/SettingsUI/Sources/EditSettingsController.swift b/submodules/SettingsUI/Sources/EditSettingsController.swift index adebb90fd5..3fd8ed5c3d 100644 --- a/submodules/SettingsUI/Sources/EditSettingsController.swift +++ b/submodules/SettingsUI/Sources/EditSettingsController.swift @@ -211,7 +211,7 @@ private enum SettingsEntry: ItemListNodeEntry { }) case let .username(theme, text, address): return ItemListDisclosureItem(theme: theme, title: text, label: address, sectionId: ItemListSectionId(self.section), style: .blocks, action: { - arguments.presentController(usernameSetupController(context: arguments.context)) + arguments.pushController(usernameSetupController(context: arguments.context)) }) case let .addAccount(theme, text): return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .center, sectionId: ItemListSectionId(self.section), style: .blocks, action: { diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index e24985854f..aedb943eeb 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -894,7 +894,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { }) } dismissInputImpl?() - presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushController(controller) }, updateRevealedPeerId: { peerId in updateState { current in return current.withUpdatedRevealedPeerId(peerId) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift index d47d531956..c8454f1466 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift @@ -239,7 +239,7 @@ public func blockedPeersController(context: AccountContext, blockedPeersContext: strongController.dismiss() })) } - presentControllerImpl?(controller, nil) + pushControllerImpl?(controller) }, removePeer: { memberId in updateState { return $0.withUpdatedRemovingPeerId(memberId) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 65132175a3..2763e0769a 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -50,6 +50,8 @@ public final class ThemePreviewController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.previewTheme, presentationStrings: self.presentationData.strings)) + self.navigationPresentation = .modal + self.isModalWhenInOverlay = true let themeName: String @@ -333,13 +335,6 @@ public final class ThemePreviewController: ViewController { })) } - override public func dismiss(completion: (() -> Void)? = nil) { - self.controllerNode.animateOut(completion: { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - completion?() - }) - } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index 0fea02cbad..af106ee1dc 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -345,6 +345,7 @@ public func usernameSetupController(context: AccountContext) -> ViewController { } let controller = ItemListController(context: context, state: signal) + controller.navigationPresentation = .modal controller.enableInteractiveDismiss = true dismissImpl = { [weak controller] in controller?.view.endEditing(true) diff --git a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift index 3aeaff3705..4e0cacf729 100644 --- a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift +++ b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift @@ -61,6 +61,13 @@ extension PeekControllerTheme { public extension NavigationControllerTheme { convenience init(presentationTheme: PresentationTheme) { - self.init(navigationBar: NavigationBarTheme(rootControllerTheme: presentationTheme), emptyAreaColor: presentationTheme.chatList.backgroundColor) + let navigationStatusBar: NavigationStatusBarStyle + switch presentationTheme.rootController.statusBarStyle { + case .black: + navigationStatusBar = .black + case .white: + navigationStatusBar = .white + } + self.init(statusBar: navigationStatusBar, navigationBar: NavigationBarTheme(rootControllerTheme: presentationTheme), emptyAreaColor: presentationTheme.chatList.backgroundColor) } } diff --git a/submodules/TelegramUI/TelegramUI/AuthorizationSequenceController.swift b/submodules/TelegramUI/TelegramUI/AuthorizationSequenceController.swift index fceecad702..58d9816535 100644 --- a/submodules/TelegramUI/TelegramUI/AuthorizationSequenceController.swift +++ b/submodules/TelegramUI/TelegramUI/AuthorizationSequenceController.swift @@ -58,7 +58,15 @@ public final class AuthorizationSequenceController: NavigationController, MFMail self.theme = theme self.openUrl = openUrl - super.init(mode: .single, theme: NavigationControllerTheme(navigationBar: AuthorizationSequenceController.navigationBarTheme(theme), emptyAreaColor: .black)) + let navigationStatusBar: NavigationStatusBarStyle + switch theme.rootController.statusBarStyle { + case .black: + navigationStatusBar = .black + case .white: + navigationStatusBar = .white + } + + super.init(mode: .single, theme: NavigationControllerTheme(statusBar: navigationStatusBar, navigationBar: AuthorizationSequenceController.navigationBarTheme(theme), emptyAreaColor: .black)) self.stateDisposable = (account.postbox.stateView() |> map { view -> InnerState in diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 269b64f0f3..acf72f0185 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -987,7 +987,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openTheme: { [weak self] message in if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { strongSelf.chatDisplayNode.dismissInput() - openChatTheme(context: strongSelf.context, message: message, present: { [weak self] c, a in + openChatTheme(context: strongSelf.context, message: message, pushController: { [weak self] c in + (self?.navigationController as? NavigationController)?.pushViewController(c) + }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a, blockInteraction: true) }) } @@ -5265,7 +5267,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, selectionLimit: selectionLimit).start(next: { generator in if let strongSelf = self { - let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) + let legacyController = LegacyController(presentation: .navigation, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) + legacyController.navigationPresentation = .modal legacyController.statusBar.statusBarStyle = strongSelf.presentationData.theme.rootController.statusBarStyle.style legacyController.controllerLoaded = { [weak legacyController] in legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true @@ -5290,7 +5293,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) })) - strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) } }, presentSelectionLimitExceeded: { guard let strongSelf = self else { @@ -5330,7 +5333,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } strongSelf.chatDisplayNode.dismissInput() - strongSelf.present(legacyController, in: .window(.root)) + legacyController.navigationPresentation = .modal + (strongSelf.navigationController as? NavigationController)?.pushViewController(legacyController) } }) }) @@ -5385,7 +5389,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.dismissInput() - strongSelf.present(legacyLocationPickerController(context: strongSelf.context, selfPeer: selfPeer, peer: peer, sendLocation: { coordinate, venue, _ in + (strongSelf.navigationController as? NavigationController)?.pushViewController(legacyLocationPickerController(context: strongSelf.context, selfPeer: selfPeer, peer: peer, sendLocation: { coordinate, venue, _ in guard let strongSelf = self else { return } @@ -5422,15 +5426,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) strongSelf.sendMessages([message]) } - }, theme: strongSelf.presentationData.theme, hasLiveLocation: !strongSelf.presentationInterfaceState.isScheduledMessages), in: .window(.root)) + }, theme: strongSelf.presentationData.theme, hasLiveLocation: !strongSelf.presentationInterfaceState.isScheduledMessages)) }) } private func presentContactPicker() { let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: self.context, title: { $0.Contacts_Title }, displayDeviceContacts: true)) + contactsController.navigationPresentation = .modal self.chatDisplayNode.dismissInput() - self.present(contactsController, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - self.controllerNavigationDisposable.set((contactsController.result |> deliverOnMainQueue).start(next: { [weak self] peer in + (self.navigationController as? NavigationController)?.pushViewController(contactsController) + self.controllerNavigationDisposable.set((contactsController.result + |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, let peer = peer { let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> switch peer { @@ -5488,7 +5494,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil) strongSelf.sendMessages([message]) } else { - strongSelf.present(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { return } @@ -5506,7 +5512,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil) strongSelf.sendMessages([message]) } - }), completed: nil, cancelled: nil), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }), completed: nil, cancelled: nil) + (strongSelf.navigationController as? NavigationController)?.pushViewController(contactController) } } })) @@ -5516,7 +5523,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private func presentPollCreation() { if case let .peer(peerId) = self.chatLocation { - self.present(createPollController(context: self.context, peerId: peerId, completion: { [weak self] message in + (self.navigationController as? NavigationController)?.pushViewController(createPollController(context: self.context, peerId: peerId, completion: { [weak self] message in guard let strongSelf = self else { return } @@ -5529,7 +5536,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) strongSelf.sendMessages([message.withUpdatedReplyToMessageId(replyMessageId)]) - }), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + })) } } @@ -6345,7 +6352,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } self.chatDisplayNode.dismissInput() - self.present(controller, in: .window(.root), blockInteraction: true) + (self.navigationController as? NavigationController)?.pushViewController(controller) } private func openPeer(peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: Message?) { @@ -6462,7 +6469,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } self.chatDisplayNode.dismissInput() - self.present(controller, in: .window(.root)) + (self.navigationController as? NavigationController)?.pushViewController(controller) } default: break @@ -7698,17 +7705,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private func openScheduledMessages() { let controller = ChatControllerImpl(context: self.context, chatLocation: self.chatLocation, subject: .scheduledMessages) + controller.navigationPresentation = .modal (self.navigationController as? NavigationController)?.pushViewController(controller) - //self.present(controller, in: .window(.root)) - } - - override public func dismiss(completion: (() -> Void)? = nil) { - if !self.isDismissed { - self.isDismissed = true - self.chatDisplayNode.animateOut(completion: { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - }) - self.updateTransitionWhenPresentedAsModal?(0.0, .animated(duration: 0.2, curve: .easeInOut)) - } } } diff --git a/submodules/TelegramUI/TelegramUI/ComposeController.swift b/submodules/TelegramUI/TelegramUI/ComposeController.swift index 004c02e37f..26cc81b859 100644 --- a/submodules/TelegramUI/TelegramUI/ComposeController.swift +++ b/submodules/TelegramUI/TelegramUI/ComposeController.swift @@ -40,13 +40,15 @@ public class ComposeController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationPresentation = .modal + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.title = self.presentationData.strings.Compose_NewMessage self.isModalWhenInOverlay = true - //self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed)) @@ -263,6 +265,6 @@ public class ComposeController: ViewController { } @objc private func cancelPressed() { - self.presentingViewController?.dismiss(animated: true, completion: nil) + (self.navigationController as? NavigationController)?.filterController(self, animated: true) } } diff --git a/submodules/TelegramUI/TelegramUI/ContactSelectionController.swift b/submodules/TelegramUI/TelegramUI/ContactSelectionController.swift index 1c263c4941..5f24ca4799 100644 --- a/submodules/TelegramUI/TelegramUI/ContactSelectionController.swift +++ b/submodules/TelegramUI/TelegramUI/ContactSelectionController.swift @@ -271,19 +271,6 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController })) } - override open func dismiss(completion: (() -> Void)? = nil) { - if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments { - switch presentationArguments.presentationAnimation { - case .modalSheet: - self.dismissed?() - self.contactsNode.animateOut(completion: completion) - case .none: - self.dismissed?() - completion?() - } - } - } - func dismissSearch() { self.deactivateSearch() } diff --git a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift index 06d3ff813c..5ac73f87a4 100644 --- a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift +++ b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift @@ -261,12 +261,8 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { }, stopLiveLocation: { params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId) }, openUrl: params.openUrl) - - if params.modal { - params.present(controller, nil) - } else { - params.navigationController?.pushViewController(controller) - } + controller.navigationPresentation = .modal + params.navigationController?.pushViewController(controller) return true case let .stickerPack(reference): let controller = StickerPackPreviewController(context: params.context, stickerPack: reference, parentNavigationController: params.navigationController) @@ -381,7 +377,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { return false } let controller = ThemePreviewController(context: params.context, previewTheme: theme, source: .media(.message(message: MessageReference(params.message), media: media))) - params.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + params.navigationController?.pushViewController(controller) } } return false @@ -430,6 +426,7 @@ func openChatInstantPage(context: AccountContext, message: Message, sourcePeerTy } let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor) + pageController.navigationPresentation = .modal navigationController.pushViewController(pageController) } break @@ -459,7 +456,7 @@ func openChatWallpaper(context: AccountContext, message: Message, present: @esca } } -func openChatTheme(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) { +func openChatTheme(context: AccountContext, message: Message, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) { for media in message.media { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url) @@ -474,7 +471,7 @@ func openChatTheme(context: AccountContext, message: Message, present: @escaping if case let .theme(slug) = resolvedUrl, let file = file { if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let theme = makePresentationTheme(data: data) { let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file)) - present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + pushController(controller) } } else { let presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift index f2bfba4f96..36359ca9ad 100644 --- a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift @@ -81,7 +81,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur } } dismissInput() - present(controller, ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) + navigationController?.pushViewController(controller) case let .channelMessage(peerId, messageId): openPeer(peerId, .chat(textInputState: nil, subject: .message(messageId))) case let .stickerPack(name): @@ -203,10 +203,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur continueWithPeer(peerId) } } - if let navigationController = navigationController { - context.sharedContext.applicationBindings.dismissNativeController() - (navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) - } + navigationController?.pushViewController(controller) } case let .wallpaper(parameter): let presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift index 99b8942f8d..f6458abe7b 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift @@ -59,6 +59,8 @@ public class PeerMediaCollectionController: TelegramBaseController { super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(self.presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none) + self.navigationPresentation = .modalInLargeLayout + self.title = self.presentationData.strings.SharedMedia_TitleAll self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -105,7 +107,18 @@ public class PeerMediaCollectionController: TelegramBaseController { return nil }, addToTransitionSurface: { view in if let strongSelf = self { - strongSelf.mediaCollectionDisplayNode.view.insertSubview(view, aboveSubview: strongSelf.mediaCollectionDisplayNode.historyNode.view) + var belowSubview: UIView? + if let historyNode = strongSelf.mediaCollectionDisplayNode.historyNode as? ChatHistoryGridNode { + if let lowestSectionNode = historyNode.lowestSectionNode() { + belowSubview = lowestSectionNode.view + } + } + strongSelf.mediaCollectionDisplayNode.historyNode + if let belowSubview = belowSubview { + strongSelf.mediaCollectionDisplayNode.historyNode.view.insertSubview(view, belowSubview: belowSubview) + } else { + strongSelf.mediaCollectionDisplayNode.historyNode.view.addSubview(view) + } } }, openUrl: { url in self?.openUrl(url) @@ -823,7 +836,7 @@ public class PeerMediaCollectionController: TelegramBaseController { }) } } - strongSelf.present(controller, in: .window(.root)) + (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) }) } diff --git a/submodules/TelegramUI/TelegramUI/PeerSelectionController.swift b/submodules/TelegramUI/TelegramUI/PeerSelectionController.swift index fc34c4ba87..f48b7cdbcb 100644 --- a/submodules/TelegramUI/TelegramUI/PeerSelectionController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerSelectionController.swift @@ -59,6 +59,9 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + + self.navigationPresentation = .modal + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.customTitle = params.title @@ -178,7 +181,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.peerSelectionNode.animateIn() + //self.peerSelectionNode.animateIn() } override public func viewDidDisappear(_ animated: Bool) { @@ -215,9 +218,4 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } } } - - override public func dismiss(completion: (() -> Void)? = nil) { - self.peerSelectionNode.view.endEditing(true) - self.peerSelectionNode.animateOut(completion: completion) - } } diff --git a/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift b/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift index 89226d864f..1b114e0110 100644 --- a/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift +++ b/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift @@ -924,7 +924,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode) -> ViewController? { - return peerInfoControllerImpl(context: context, peer: peer, mode: mode) + let controller = peerInfoControllerImpl(context: context, peer: peer, mode: mode) + controller?.navigationPresentation = .modalInLargeLayout + return controller } public func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) { @@ -995,8 +997,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { openAddContactImpl(context: context, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, label: label, present: present, pushController: pushController, completed: completed) } - public func openAddPersonContact(context: AccountContext, peerId: PeerId, present: @escaping (ViewController, Any?) -> Void) { - openAddPersonContactImpl(context: context, peerId: peerId, present: present) + public func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) { + openAddPersonContactImpl(context: context, peerId: peerId, pushController: pushController, present: present) } public func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController { diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index db17897404..2deb8cc17b 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -472,12 +472,4 @@ public final class WebSearchController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) } - - override public func dismiss(completion: (() -> Void)? = nil) { - self.navigationContentNode?.deactivate() - self.controllerNode.animateOut(completion: { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - completion?() - }) - } }