From 75d26e613b29a6ef65477c79515ee44cec85e904 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 11 Nov 2020 15:26:25 -0500 Subject: [PATCH] Add account switching animation --- Pachyderm/Client.swift | 1 - Tusker.xcodeproj/project.pbxproj | 4 + Tusker/SceneDelegate.swift | 46 ++++++---- .../FastAccountSwitcherViewController.swift | 18 ++-- ...ountSwitchingContainerViewController.swift | 84 +++++++++++++++++++ .../PreferencesNavigationController.swift | 4 +- 6 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 Tusker/Screens/Main/AccountSwitchingContainerViewController.swift diff --git a/Pachyderm/Client.swift b/Pachyderm/Client.swift index 4f7ab784..1c5f6cc7 100644 --- a/Pachyderm/Client.swift +++ b/Pachyderm/Client.swift @@ -76,7 +76,6 @@ public class Client { return } guard let result = try? Client.decoder.decode(Result.self, from: data) else { - print(request) completion(.failure(.invalidModel)) return } diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 774cc56c..1ed72b36 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -297,6 +297,7 @@ D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */; }; D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */; }; D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; }; + D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -649,6 +650,7 @@ D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CrashReporterViewController.xib; sourceTree = ""; }; D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = ""; }; D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = ""; }; + D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSwitchingContainerViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -975,6 +977,7 @@ D641C782213DD7F0004B4513 /* Main */ = { isa = PBXGroup; children = ( + D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */, D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */, 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */, D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */, @@ -1971,6 +1974,7 @@ D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */, D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */, 04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */, + D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */, D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */, D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */, D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */, diff --git a/Tusker/SceneDelegate.swift b/Tusker/SceneDelegate.swift index 075f1c30..3ee0eec3 100644 --- a/Tusker/SceneDelegate.swift +++ b/Tusker/SceneDelegate.swift @@ -130,50 +130,64 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func showAppOrOnboardingUI(session: UISceneSession? = nil) { let session = session ?? window!.windowScene!.session if LocalData.shared.onboardingComplete { + let account = LocalData.shared.getMostRecentAccount()! if session.mastodonController == nil { - let account = LocalData.shared.getMostRecentAccount()! session.mastodonController = MastodonController.getForAccount(account) } - showAppUI() + activateAccount(account, animated: false) } else { - showOnboardingUI() + window!.rootViewController = createOnboardingUI() } } - func activateAccount(_ account: LocalData.UserAccountInfo) { + func activateAccount(_ account: LocalData.UserAccountInfo, animated: Bool) { + let oldIndex = LocalData.shared.accounts.firstIndex(where: { $0.id == LocalData.shared.mostRecentAccountID })! + let newIndex = LocalData.shared.accounts.firstIndex(of: account)! + LocalData.shared.setMostRecentAccount(account) window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account) - showAppUI() + + let newRoot = createAppUI() + if let container = window?.rootViewController as? AccountSwitchingContainerViewController { + let direction: AccountSwitchingContainerViewController.AnimationDirection + if animated { + direction = newIndex > oldIndex ? .upwards : .downwards + } else { + direction = .none + } + container.setRoot(newRoot, animating: direction) + } else { + window!.rootViewController = AccountSwitchingContainerViewController(root: newRoot) + } } func logoutCurrent() { LocalData.shared.removeAccount(LocalData.shared.getMostRecentAccount()!) if LocalData.shared.onboardingComplete { - activateAccount(LocalData.shared.accounts.first!) + activateAccount(LocalData.shared.accounts.first!, animated: false) } else { - showOnboardingUI() + window!.rootViewController = createOnboardingUI() } } - func showAppUI() { + func createAppUI() -> TuskerRootViewController { let mastodonController = window!.windowScene!.session.mastodonController! mastodonController.getOwnAccount() mastodonController.getOwnInstance() - let rootController: UIViewController - if #available(iOS 14.0, *) { - rootController = MainSplitViewController(mastodonController: mastodonController) + if #available(iOS 14.0, *), + UIDevice.current.userInterfaceIdiom != .phone { + return MainSplitViewController(mastodonController: mastodonController) } else { - rootController = MainTabBarViewController(mastodonController: mastodonController) + return MainTabBarViewController(mastodonController: mastodonController) } - window!.rootViewController = rootController } - func showOnboardingUI() { + func createOnboardingUI() -> UIViewController { let onboarding = OnboardingViewController() onboarding.onboardingDelegate = self - window!.rootViewController = onboarding + return onboarding } @objc func themePrefChanged() { @@ -184,7 +198,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { extension SceneDelegate: OnboardingViewControllerDelegate { func didFinishOnboarding(account: LocalData.UserAccountInfo) { - activateAccount(account) + activateAccount(account, animated: false) } } diff --git a/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift b/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift index 1eae776f..a43fb8b2 100644 --- a/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift +++ b/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift @@ -86,15 +86,16 @@ class FastAccountSwitcherViewController: UIViewController { } } - func hide() { + func hide(completion: (() -> Void)? = nil) { lastSelectedAccountViewIndex = nil selectionChangedFeedbackGenerator = nil - UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) { + UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) { self.view.alpha = 0 } completion: { (_) in self.view.alpha = 1 self.view.isHidden = true + completion?() } } @@ -128,7 +129,12 @@ class FastAccountSwitcherViewController: UIViewController { selectionChangedFeedbackGenerator?.impactOccurred() } selectionChangedFeedbackGenerator = nil - (view.window!.windowScene!.delegate as! SceneDelegate).activateAccount(account) + + hide() { + (self.view.window!.windowScene!.delegate as! SceneDelegate).activateAccount(account, animated: true) + } + } else { + hide() } } @@ -152,7 +158,6 @@ class FastAccountSwitcherViewController: UIViewController { let location = recognizer.location(in: view) if let index = lastSelectedAccountViewIndex { switchAccount(newIndex: index) - hide() } else if !(delegate?.fastAccountSwitcher(self, triggerZoneContains: location) ?? false) { hide() } @@ -172,7 +177,6 @@ class FastAccountSwitcherViewController: UIViewController { case .ended: if let index = lastSelectedAccountViewIndex { switchAccount(newIndex: index) - hide() } default: @@ -207,9 +211,9 @@ class FastAccountSwitcherViewController: UIViewController { touchBeganFeedbackWorkItem = nil switchAccount(newIndex: tappedIndex) + } else { + hide() } - - hide() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { diff --git a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift new file mode 100644 index 00000000..88ecfa40 --- /dev/null +++ b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift @@ -0,0 +1,84 @@ +// +// AccountSwitchingContainerViewController.swift +// Tusker +// +// Created by Shadowfacts on 11/11/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit + +class AccountSwitchingContainerViewController: UIViewController { + + private(set) var root: TuskerRootViewController + + init(root: TuskerRootViewController) { + 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, animating direction: AnimationDirection) { + let oldRoot = self.root + if direction == .none { + oldRoot.removeViewAndController() + } + self.root = newRoot + embedChild(newRoot) + + if direction != .none { + if UIAccessibility.prefersCrossFadeTransitionsBackwardsCompat { + 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 + + newRoot.view.transform = CGAffineTransform(translationX: 0, y: newInitialOffset) + + UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) { + newRoot.view.transform = .identity + oldRoot.view.transform = CGAffineTransform(translationX: 0, y: -newInitialOffset) + } completion: { (_) in + oldRoot.removeViewAndController() + } + } + } + } +} + +extension AccountSwitchingContainerViewController { + enum AnimationDirection { + case none, downwards, upwards + } +} + +extension AccountSwitchingContainerViewController: TuskerRootViewController { + func presentCompose() { + root.presentCompose() + } + + func select(tab: MainTabBarViewController.Tab) { + root.select(tab: tab) + } + + func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? { + root.getTabController(tab: tab) + } +} diff --git a/Tusker/Screens/Preferences/PreferencesNavigationController.swift b/Tusker/Screens/Preferences/PreferencesNavigationController.swift index 302eb832..35e1e3d5 100644 --- a/Tusker/Screens/Preferences/PreferencesNavigationController.swift +++ b/Tusker/Screens/Preferences/PreferencesNavigationController.swift @@ -69,7 +69,7 @@ class PreferencesNavigationController: UINavigationController { let account = notification.userInfo!["account"] as! LocalData.UserAccountInfo isSwitchingAccounts = true dismiss(animated: true) { // dismiss preferences - sceneDelegate.activateAccount(account) + sceneDelegate.activateAccount(account, animated: true) } } @@ -93,7 +93,7 @@ extension PreferencesNavigationController: OnboardingViewControllerDelegate { let sceneDelegate = self.view.window!.windowScene!.delegate as! SceneDelegate self.dismiss(animated: true) { // dismiss instance selector self.dismiss(animated: true) { // dismiss preferences - sceneDelegate.activateAccount(account) + sceneDelegate.activateAccount(account, animated: false) } } }