/* Copyright 2020 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import Foundation /// SplitViewCoordinatorParameters input parameters class SplitViewCoordinatorParameters { let router: RootRouterType let userSessionsService: UserSessionsService let appNavigator: AppNavigatorProtocol init(router: RootRouterType, userSessionsService: UserSessionsService, appNavigator: AppNavigatorProtocol) { self.router = router self.userSessionsService = userSessionsService self.appNavigator = appNavigator } } final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { // MARK: - Properties // MARK: Private private let parameters: SplitViewCoordinatorParameters private let splitViewController: UISplitViewController private weak var masterPresentable: SplitViewMasterPresentable? private var detailNavigationController: UINavigationController? private weak var tabBarCoordinator: TabBarCoordinatorType? // Indicate if coordinator has been started once private var hasStartedOnce: Bool = false // MARK: Public var childCoordinators: [Coordinator] = [] weak var delegate: SplitViewCoordinatorDelegate? // MARK: - Setup init(parameters: SplitViewCoordinatorParameters) { self.parameters = parameters let splitViewController = RiotSplitViewController() splitViewController.preferredDisplayMode = .allVisible self.splitViewController = splitViewController } // MARK: - Public methods func start() { self.start(with: nil) } func start(with spaceId: String?) { if hasStartedOnce == false { self.hasStartedOnce = true self.splitViewController.delegate = self let tabBarCoordinator = self.createTabBarCoordinator() tabBarCoordinator.delegate = self tabBarCoordinator.splitViewMasterPresentableDelegate = self tabBarCoordinator.start(with: spaceId) let detailNavigationController = self.createDetailNavigationController() self.splitViewController.viewControllers = [tabBarCoordinator.toPresentable(), detailNavigationController] self.add(childCoordinator: tabBarCoordinator) self.tabBarCoordinator = tabBarCoordinator self.masterPresentable = tabBarCoordinator self.detailNavigationController = detailNavigationController self.parameters.router.setRootModule(self.splitViewController) } else { // Pop to home screen when selecting a new space self.popToHome(animated: true) { // Update tabBarCoordinator selected space self.tabBarCoordinator?.start(with: spaceId) } } } func toPresentable() -> UIViewController { return self.splitViewController } // TODO: Do not expose publicly this method func restorePlaceholderDetails() { // Be sure that the primary is then visible too. if splitViewController.displayMode == .primaryHidden { splitViewController.preferredDisplayMode = .allVisible } if splitViewController.viewControllers.count == 2 { let mainViewController = splitViewController.viewControllers[0] let emptyDetailsViewController = self.createPlaceholderDetailsViewController() splitViewController.viewControllers = [mainViewController, emptyDetailsViewController] } // Release the current selected item (room/contact/group...). self.tabBarCoordinator?.releaseSelectedItems() } func popToHome(animated: Bool, completion: (() -> Void)?) { if let secondNavController = self.detailNavigationController { secondNavController.popToRootViewController(animated: animated) } // Force back to the main screen if this is not the one that is displayed self.tabBarCoordinator?.popToHome(animated: animated, completion: completion) } // MARK: - Private methods private func createPlaceholderDetailsViewController() -> UIViewController { return PlaceholderDetailViewController.instantiate() } private func createTabBarCoordinator() -> TabBarCoordinator { let coordinatorParameters = TabBarCoordinatorParameters(userSessionsService: self.parameters.userSessionsService, appNavigator: self.parameters.appNavigator) let tabBarCoordinator = TabBarCoordinator(parameters: coordinatorParameters) tabBarCoordinator.delegate = self return tabBarCoordinator } private func createDetailNavigationController() -> UINavigationController { let placeholderDetailViewController = self.createPlaceholderDetailsViewController() let detailNavigationController = RiotNavigationController(rootViewController: placeholderDetailViewController) return detailNavigationController } } // MARK: - UISplitViewControllerDelegate extension SplitViewCoordinator: UISplitViewControllerDelegate { func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? { if let detailViewController = self.masterPresentable?.secondViewControllerWhenSeparatedFromPrimary() { return detailViewController } // Else return the default empty details view controller from the storyboard. // Be sure that the primary is then visible too. if splitViewController.displayMode == .primaryHidden { splitViewController.preferredDisplayMode = .allVisible } return self.createPlaceholderDetailsViewController() } func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool { return self.masterPresentable?.collapseDetailViewController ?? false } } /// MARK: - UINavigationControllerDelegate extension SplitViewCoordinator: TabBarCoordinatorDelegate { func tabBarCoordinatorDidCompleteAuthentication(_ coordinator: TabBarCoordinatorType) { self.delegate?.splitViewCoordinatorDidCompleteAuthentication(self) } } /// MARK: - SplitViewMasterPresentableDelegate extension SplitViewCoordinator: SplitViewMasterPresentableDelegate { func splitViewMasterPresentable(_ presentable: Presentable, wantsToDisplay detailPresentable: Presentable) { MXLog.debug("[SplitViewCoordinator] splitViewMasterPresentable: \(presentable) wantsToDisplay detailPresentable: \(detailPresentable)") guard let detailNavigationController = self.detailNavigationController else { MXLog.debug("[SplitViewCoordinator] splitViewMasterPresentable: Failed to display because detailNavigationController is nil") return } detailNavigationController.viewControllers = [detailPresentable.toPresentable()] self.splitViewController.showDetailViewController(detailNavigationController, sender: nil) } }