diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 4572c614..df0cc701 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -2536,6 +2536,8 @@ isa = PBXGroup; children = ( DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */, + DB852D1A26FAED0100FC9D81 /* Sidebar */, + DB8AF54E25C13703002E6C99 /* MainTab */, ); path = Root; sourceTree = ""; @@ -2652,8 +2654,6 @@ 2D7631A425C1532200929FB9 /* Share */, DB6180E426391A500018D199 /* Transition */, DB852D1D26FB021900FC9D81 /* Root */, - DB852D1A26FAED0100FC9D81 /* Sidebar */, - DB8AF54E25C13703002E6C99 /* MainTab */, DB01409B25C40BB600F9F3CF /* Onboarding */, DB9F58ED26EF435800E7BBE9 /* Account */, 2D38F1D325CD463600561493 /* HomeTimeline */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 4445eac2..f5e0928d 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -18,6 +18,8 @@ final public class SceneCoordinator { let id = UUID().uuidString + weak var splitViewController: RootSplitViewController? + init(scene: UIScene, sceneDelegate: SceneDelegate, appContext: AppContext) { self.scene = scene self.sceneDelegate = sceneDelegate @@ -119,6 +121,7 @@ extension SceneCoordinator { func setup() { let splitViewController = RootSplitViewController(context: appContext, coordinator: self) + self.splitViewController = splitViewController sceneDelegate.window?.rootViewController = splitViewController } @@ -172,8 +175,14 @@ extension SceneCoordinator { switch transition { case .show: - presentingViewController.show(viewController, sender: sender) - + if let splitViewController = splitViewController, !splitViewController.isCollapsed, + let supplementaryViewController = splitViewController.viewController(for: .supplementary) as? UINavigationController, + (supplementaryViewController === presentingViewController || supplementaryViewController.viewControllers.contains(presentingViewController)) + { + fallthrough + } else { + presentingViewController.show(viewController, sender: sender) + } case .showDetail: let navigationController = AdaptiveStatusBarStyleNavigationController(rootViewController: viewController) presentingViewController.showDetailViewController(navigationController, sender: sender) diff --git a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift index 02a564d8..d11870ed 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift @@ -111,10 +111,10 @@ extension StatusProviderFacade { if provider.navigationController == nil { let from = provider.presentingViewController ?? provider provider.dismiss(animated: true) { - provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .showDetail) + provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show) } } else { - provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: provider, transition: .showDetail) + provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: provider, transition: .show) } } } diff --git a/Mastodon/Scene/MainTab/MainTabBarController+Wizard.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController+Wizard.swift similarity index 100% rename from Mastodon/Scene/MainTab/MainTabBarController+Wizard.swift rename to Mastodon/Scene/Root/MainTab/MainTabBarController+Wizard.swift diff --git a/Mastodon/Scene/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift similarity index 99% rename from Mastodon/Scene/MainTab/MainTabBarController.swift rename to Mastodon/Scene/Root/MainTab/MainTabBarController.swift index b184ff25..546988be 100644 --- a/Mastodon/Scene/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -24,7 +24,7 @@ class MainTabBarController: UITabBarController { let wizard = Wizard() - var currentTab = Tab.home + var currentTab = CurrentValueSubject(.home) enum Tab: Int, CaseIterable { case home @@ -361,10 +361,10 @@ extension MainTabBarController: UITabBarControllerDelegate { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select %s", ((#file as NSString).lastPathComponent), #line, #function, viewController.debugDescription) defer { if let tab = Tab(rawValue: tabBarController.selectedIndex) { - currentTab = tab + currentTab.value = tab } } - guard currentTab.rawValue == tabBarController.selectedIndex, + guard currentTab.value.rawValue == tabBarController.selectedIndex, let navigationController = viewController as? UINavigationController, navigationController.viewControllers.count == 1, let scrollViewContainer = navigationController.topViewController as? ScrollViewContainer else { @@ -535,7 +535,7 @@ extension MainTabBarController { let previousTab = Tab(rawValue: selectedIndex) selectedIndex = index if let tab = Tab(rawValue: index) { - currentTab = tab + currentTab.value = tab } if let previousTab = previousTab { diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index 64021915..7437b259 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -7,9 +7,12 @@ import os.log import UIKit +import Combine final class RootSplitViewController: UISplitViewController, NeedsDependency { + var disposeBag = Set() + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -21,6 +24,13 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { sidebarViewController.delegate = self return sidebarViewController }() + + var currentSupplementaryTab: MainTabBarController.Tab = .home + private(set) lazy var supplementaryViewControllers: [UIViewController] = { + return MainTabBarController.Tab.allCases.map { tab in + tab.viewController(context: context, coordinator: coordinator) + } + }() private(set) lazy var mainTabBarController = MainTabBarController(context: context, coordinator: coordinator) @@ -32,6 +42,7 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { primaryBackgroundStyle = .sidebar preferredDisplayMode = .oneBesideSecondary preferredSplitBehavior = .tile + delegate = self if #available(iOS 14.5, *) { displayModeButtonVisibility = .always @@ -40,7 +51,7 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { } setViewController(sidebarViewController, for: .primary) - setViewController(mainTabBarController.viewControllers!.first, for: .supplementary) + setViewController(supplementaryViewControllers[0], for: .supplementary) setViewController(UIViewController(), for: .secondary) setViewController(mainTabBarController, for: .compact) } @@ -61,6 +72,18 @@ extension RootSplitViewController { super.viewDidLoad() updateBehavior(size: view.frame.size) + + mainTabBarController.currentTab + .receive(on: DispatchQueue.main) + .sink { [weak self] tab in + guard let self = self else { return } + guard tab != self.currentSupplementaryTab else { return } + guard let index = MainTabBarController.Tab.allCases.firstIndex(of: tab) else { return } + self.currentSupplementaryTab = tab + self.setViewController(self.supplementaryViewControllers[index], for: .supplementary) + + } + .store(in: &disposeBag) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -86,16 +109,76 @@ extension RootSplitViewController { extension RootSplitViewController: SidebarViewControllerDelegate { func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) { - // FIXME: remove hard code - switch tab { - case .home: - setViewController(mainTabBarController._viewControllers[0], for: .supplementary) - case .search: - setViewController(mainTabBarController._viewControllers[1], for: .supplementary) - case .notification: - setViewController(mainTabBarController._viewControllers[2], for: .supplementary) - case .me: - setViewController(mainTabBarController._viewControllers[3], for: .supplementary) + guard let index = MainTabBarController.Tab.allCases.firstIndex(of: tab) else { + assertionFailure() + return + } + currentSupplementaryTab = tab + setViewController(supplementaryViewControllers[index], for: .supplementary) + } +} + +// MARK: - UISplitViewControllerDelegate +extension RootSplitViewController: UISplitViewControllerDelegate { + + // .regular to .compact + // move navigation stack from .supplementary & .secondary to .compact + func splitViewController( + _ svc: UISplitViewController, + topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column + ) -> UISplitViewController.Column { + switch proposedTopColumn { + case .compact: + guard let index = MainTabBarController.Tab.allCases.firstIndex(of: currentSupplementaryTab) else { + assertionFailure() + break + } + mainTabBarController.selectedIndex = index + mainTabBarController.currentTab.value = currentSupplementaryTab + + guard let navigationController = mainTabBarController.selectedViewController as? UINavigationController else { break } + navigationController.popToRootViewController(animated: false) + var viewControllers = navigationController.viewControllers // init navigation stack with topMost + + if let supplementaryNavigationController = viewController(for: .supplementary) as? UINavigationController { + // append supplementary + viewControllers.append(contentsOf: supplementaryNavigationController.popToRootViewController(animated: true) ?? []) + } + if let secondaryNavigationController = viewController(for: .secondary) as? UINavigationController { + // append secondary + viewControllers.append(contentsOf: secondaryNavigationController.popToRootViewController(animated: true) ?? []) + } + // set navigation stack + navigationController.setViewControllers(viewControllers, animated: false) + + default: + assertionFailure() + } + + return proposedTopColumn + } + + // .compact to .regular + // restore navigation stack to .supplementary & .secondary + func splitViewController( + _ svc: UISplitViewController, + displayModeForExpandingToProposedDisplayMode proposedDisplayMode: UISplitViewController.DisplayMode + ) -> UISplitViewController.DisplayMode { + + return proposedDisplayMode + } + + func splitViewController( + _ splitViewController: UISplitViewController, + show vc: UIViewController, + sender: Any? + ) -> Bool { + if !splitViewController.isCollapsed { + // display in .secondary when expand + splitViewController.showDetailViewController(vc, sender: sender) + return true + } else { + return false } } } diff --git a/Mastodon/Scene/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift similarity index 100% rename from Mastodon/Scene/Sidebar/SidebarViewController.swift rename to Mastodon/Scene/Root/Sidebar/SidebarViewController.swift diff --git a/Mastodon/Scene/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift similarity index 100% rename from Mastodon/Scene/Sidebar/SidebarViewModel.swift rename to Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift