// // AccountSwitchingContainerViewController.swift // Tusker // // Created by Shadowfacts on 11/11/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import UIKit import ScreenCorners class AccountSwitchingContainerViewController: UIViewController { private var currentAccountID: String private(set) var root: TuskerRootViewController private var userActivities: [String: NSUserActivity] = [:] init(root: TuskerRootViewController, for account: LocalData.UserAccountInfo) { self.currentAccountID = account.id self.root = root super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() embedChild(root) } func setRoot(_ newRoot: TuskerRootViewController, for account: LocalData.UserAccountInfo, animating direction: AnimationDirection) { let oldRoot = self.root if direction == .none { oldRoot.removeViewAndController() } if let activity = oldRoot.stateRestorationActivity() { stateRestorationLogger.debug("AccountSwitchingContainer: saving \(activity.activityType, privacy: .public) for \(self.currentAccountID, privacy: .public)") userActivities[currentAccountID] = activity } self.currentAccountID = account.id self.root = newRoot embedChild(newRoot) if let activity = userActivities.removeValue(forKey: account.id) { stateRestorationLogger.debug("AccountSwitchingContainer: restoring \(activity.activityType, privacy: .public) for \(account.id, privacy: .public)") let context = StateRestorationUserActivityHandlingContext(root: newRoot) _ = activity.handleResume(manager: UserActivityManager(scene: view.window!.windowScene!, context: context)) context.finalize(activity: activity) } if direction != .none { if UIAccessibility.prefersCrossFadeTransitions { newRoot.view.alpha = 0 UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseInOut) { newRoot.view.alpha = 1 oldRoot.view.alpha = 0 } completion: { (_) in oldRoot.removeViewAndController() } } else { let sign: CGFloat = direction == .downwards ? -1 : 1 let newInitialOffset = sign * view.bounds.height let scale: CGFloat = 0.75 newRoot.view.transform = CGAffineTransform(translationX: 0, y: newInitialOffset).scaledBy(x: 0.9, y: 0.9) newRoot.view.layer.masksToBounds = true newRoot.view.layer.cornerCurve = .continuous newRoot.view.layer.cornerRadius = view.window?.screen.displayCornerRadius ?? 0 oldRoot.view.layer.masksToBounds = true oldRoot.view.layer.cornerCurve = .continuous oldRoot.view.layer.cornerRadius = view.window?.screen.displayCornerRadius ?? 0 // only one edge is affected in each direction, i have no idea why if direction == .upwards { oldRoot.additionalSafeAreaInsets.bottom = view.safeAreaInsets.bottom } else { oldRoot.additionalSafeAreaInsets.top = view.safeAreaInsets.top } UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseInOut) { oldRoot.view.transform = CGAffineTransform(translationX: 0, y: -newInitialOffset).scaledBy(x: scale, y: scale) newRoot.view.transform = .identity } completion: { (_) in oldRoot.removeViewAndController() newRoot.view.layer.masksToBounds = false } } } } } extension AccountSwitchingContainerViewController { enum AnimationDirection { case none, downwards, upwards } } extension AccountSwitchingContainerViewController: TuskerRootViewController { func stateRestorationActivity() -> NSUserActivity? { loadViewIfNeeded() return root.stateRestorationActivity() } func compose(editing draft: Draft?, animated: Bool, isDucked: Bool) { loadViewIfNeeded() root.compose(editing: draft, animated: animated, isDucked: isDucked) } func select(route: TuskerRoute, animated: Bool) { loadViewIfNeeded() root.select(route: route, animated: animated) } func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? { loadViewIfNeeded() return root.getTabController(tab: tab) } func getNavigationDelegate() -> TuskerNavigationDelegate? { loadViewIfNeeded() return root.getNavigationDelegate() } func getNavigationController() -> NavigationControllerProtocol { loadViewIfNeeded() return root.getNavigationController() } func performSearch(query: String) { loadViewIfNeeded() root.performSearch(query: query) } func presentPreferences(completion: (() -> Void)?) { loadViewIfNeeded() root.presentPreferences(completion: completion) } func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult { loadViewIfNeeded() // TODO: check if fast account switcher is being presented? return root.handleStatusBarTapped(xPosition: xPosition) } } extension AccountSwitchingContainerViewController: BackgroundableViewController { func sceneDidEnterBackground() { if let backgroundable = root as? BackgroundableViewController { backgroundable.sceneDidEnterBackground() } } }