// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // import Foundation @objc class ConversationSplitViewController: UISplitViewController { private let conversationListVC = ConversationListViewController() private let detailPlaceholderVC = NoSelectedConversationViewController() private lazy var primaryNavController = SignalsNavigationController(rootViewController: conversationListVC) private lazy var detailNavController = OWSNavigationController() @objc private(set) weak var selectedConversationViewController: ConversationViewController? /// The thread, if any, that is currently presented in the view hieararchy. It may be currently /// covered by a modal presentation or a pushed view controller. @objc var selectedThread: TSThread? { // If the placeholder view is in the view hierarchy, there is no selected thread. guard detailPlaceholderVC.view.superview == nil else { return nil } guard let selectedConversationViewController = selectedConversationViewController else { return nil } // In order to not show selected when collapsed during an interactive dismissal, // we verify the conversation is still in the nav stack when collapsed. There is // no interactive dismissal when expanded, so we don't have to do any special check. guard !isCollapsed || primaryNavController.viewControllers.contains(selectedConversationViewController) else { return nil } return selectedConversationViewController.thread } /// Returns the currently selected thread if it is visible on screen, otherwise /// returns nil. @objc var visibleThread: TSThread? { guard view.window?.isKeyWindow == true else { return nil } guard selectedConversationViewController?.isViewVisible == true else { return nil } return selectedThread } @objc var topViewController: UIViewController? { guard !isCollapsed else { return primaryNavController.topViewController } return detailNavController.topViewController ?? primaryNavController.topViewController } @objc init() { super.init(nibName: nil, bundle: nil) viewControllers = [primaryNavController, detailPlaceholderVC] primaryNavController.delegate = self delegate = self // If this is not an iPad we want to always show the collapsed mode, even // if the size class is regular (portrait mode on large phones). On iPad, // if there is space we want to always show the conversation list. preferredDisplayMode = UIDevice.current.isIPad ? .allVisible : .primaryHidden } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override var preferredStatusBarStyle: UIStatusBarStyle { return Theme.isDarkThemeEnabled ? .lightContent : .default } @objc func showNewConversationView() { conversationListVC.showNewConversationView() } @objc(closeSelectedConversationAnimated:) func closeSelectedConversation(animated: Bool) { guard let selectedConversationViewController = selectedConversationViewController else { return } if isCollapsed { // If we're currently displaying the conversation in the primary nav controller, remove it // and everything it pushed to the navigation stack from the nav controller. We don't want // to just pop to root as we might have opened this conversation from the archive. if let selectedConversationIndex = primaryNavController.viewControllers.firstIndex(of: selectedConversationViewController) { let trimmedViewControllers = Array(primaryNavController.viewControllers[0.. Bool { // If we're currently showing the placeholder view, we want to do nothing with in // when collapsing into a signle nav controller without a side panel. guard secondaryViewController != detailPlaceholderVC else { return true } assert(secondaryViewController == detailNavController) // Move all the views from the detail nav controller onto the primary nav controller. primaryNavController.viewControllers += detailNavController.viewControllers return true } func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? { assert(primaryViewController == primaryNavController) // See if the current conversation is currently in the view hierarchy. If not, // show the placeholder view as no conversation is selected. The conversation // was likely popped from the stack while the split view was collapsed. guard let currentConversationVC = selectedConversationViewController, let conversationVCIndex = primaryNavController.viewControllers.firstIndex(of: currentConversationVC) else { self.selectedConversationViewController = nil return detailPlaceholderVC } // Move everything on the nav stack from the conversation view on back onto // the detail nav controller. let allViewControllers = primaryNavController.viewControllers primaryNavController.viewControllers = Array(allViewControllers[0..