diff --git a/Tusker/MainSceneDelegate.swift b/Tusker/MainSceneDelegate.swift index 4b6265eb..ebb17c9a 100644 --- a/Tusker/MainSceneDelegate.swift +++ b/Tusker/MainSceneDelegate.swift @@ -17,6 +17,10 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate { private var launchActivity: NSUserActivity? + var rootViewController: TuskerRootViewController? { + window?.rootViewController as? TuskerRootViewController + } + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } @@ -56,7 +60,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate { if url.host == "x-callback-url" { _ = XCBManager.handle(url: url) } else if var components = URLComponents(url: url, resolvingAgainstBaseURL: false), - let rootViewController = window!.rootViewController as? TuskerRootViewController { + let rootViewController = rootViewController { components.scheme = "https" let query = components.string! rootViewController.performSearch(query: query) @@ -202,6 +206,12 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate { window?.overrideUserInterfaceStyle = Preferences.shared.theme } + func showAddAccount() { + rootViewController?.presentPreferences { + NotificationCenter.default.post(name: .addAccount, object: nil) + } + } + } extension MainSceneDelegate: OnboardingViewControllerDelegate { diff --git a/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift b/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift index b3f4631f..0c4fc367 100644 --- a/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift +++ b/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift @@ -101,7 +101,13 @@ class FastAccountSwitcherViewController: UIViewController { private func createAccountViews() { accountsStack.arrangedSubviews.forEach { $0.removeFromSuperview() } - accountViews = [] + + let addAccountPlaceholder = FastSwitchingAccountView() + accountsStack.addArrangedSubview(addAccountPlaceholder) + + accountViews = [ + addAccountPlaceholder + ] for account in LocalData.shared.accounts { let accountView = FastSwitchingAccountView(account: account) @@ -122,19 +128,30 @@ class FastAccountSwitcherViewController: UIViewController { } private func switchAccount(newIndex: Int, hapticFeedback: Bool = true) { - let account = LocalData.shared.accounts[newIndex] - - if account.id != LocalData.shared.mostRecentAccountID { + if newIndex == 0 { // add account placeholder if hapticFeedback { selectionChangedFeedbackGenerator?.impactOccurred() } selectionChangedFeedbackGenerator = nil hide() { - (self.view.window!.windowScene!.delegate as! MainSceneDelegate).activateAccount(account, animated: true) + (self.view.window!.windowScene!.delegate as! MainSceneDelegate).showAddAccount() } } else { - hide() + let account = LocalData.shared.accounts[newIndex - 1] + + if account.id != LocalData.shared.mostRecentAccountID { + if hapticFeedback { + selectionChangedFeedbackGenerator?.impactOccurred() + } + selectionChangedFeedbackGenerator = nil + + hide() { + (self.view.window!.windowScene!.delegate as! MainSceneDelegate).activateAccount(account, animated: true) + } + } else { + hide() + } } } diff --git a/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift b/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift index 6ef93d65..48241f37 100644 --- a/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift +++ b/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift @@ -10,8 +10,6 @@ import UIKit class FastSwitchingAccountView: UIView { - let account: LocalData.UserAccountInfo - private static let selectedColor = UIColor { (traits) in if traits.userInterfaceStyle == .dark { return UIColor(hue: 211 / 360, saturation: 85 / 100, brightness: 100 / 100, alpha: 1) @@ -39,21 +37,32 @@ class FastSwitchingAccountView: UIView { private let usernameLabel = UILabel() private let instanceLabel = UILabel() + private let avatarImageView = UIImageView() private var avatarRequest: ImageCache.Request? init(account: LocalData.UserAccountInfo) { - self.account = account - super.init(frame: .zero) - + commonInit() + setupAccount(account: account) + } + + init() { + super.init(frame: .zero) + commonInit() + setupPlaceholder() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { usernameLabel.textColor = .white usernameLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .headline), size: 0) - usernameLabel.text = account.username instanceLabel.textColor = .white instanceLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .subheadline), size: 0) - instanceLabel.text = account.instanceURL.host! let stackView = UIStackView(arrangedSubviews: [ usernameLabel, @@ -64,12 +73,11 @@ class FastSwitchingAccountView: UIView { stackView.alignment = .trailing addSubview(stackView) - let avatarImageView = UIImageView() avatarImageView.translatesAutoresizingMaskIntoConstraints = false avatarImageView.layer.masksToBounds = true avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 40 avatarImageView.image = UIImage(systemName: Preferences.shared.avatarStyle == .circle ? "person.crop.circle" : "person.crop.square") - avatarImageView.contentMode = .scaleAspectFit + avatarImageView.contentMode = .scaleAspectFill addSubview(avatarImageView) NSLayoutConstraint.activate([ @@ -84,22 +92,30 @@ class FastSwitchingAccountView: UIView { stackView.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor), ]) + updateLabelColors() + } + + private func setupAccount(account: LocalData.UserAccountInfo) { + usernameLabel.text = account.username + instanceLabel.text = account.instanceURL.host! let controller = MastodonController.getForAccount(account) controller.getOwnAccount { [weak self] (result) in guard let self = self, case let .success(account) = result, let avatar = account.avatar else { return } - self.avatarRequest = ImageCache.avatars.get(avatar) { [weak avatarImageView] (_, image) in - guard let avatarImageView = avatarImageView, let image = image else { return } + self.avatarRequest = ImageCache.avatars.get(avatar) { [weak self] (_, image) in + guard let self = self, let image = image else { return } DispatchQueue.main.async { - avatarImageView.image = image + self.avatarImageView.image = image } } } } - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + private func setupPlaceholder() { + usernameLabel.text = "Add Account" + instanceLabel.isHidden = true + avatarImageView.image = UIImage(systemName: "plus") } private func updateLabelColors() { @@ -113,6 +129,7 @@ class FastSwitchingAccountView: UIView { } usernameLabel.textColor = color instanceLabel.textColor = color + avatarImageView.tintColor = color } } diff --git a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift index c30a0837..d5571da9 100644 --- a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift +++ b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift @@ -86,8 +86,14 @@ extension AccountSwitchingContainerViewController: TuskerRootViewController { } func performSearch(query: String) { + loadViewIfNeeded() root.performSearch(query: query) } + + func presentPreferences(completion: (() -> Void)?) { + loadViewIfNeeded() + root.presentPreferences(completion: completion) + } } extension AccountSwitchingContainerViewController: BackgroundableViewController { diff --git a/Tusker/Screens/Main/MainSplitViewController.swift b/Tusker/Screens/Main/MainSplitViewController.swift index d9d7170c..fe6b8fdf 100644 --- a/Tusker/Screens/Main/MainSplitViewController.swift +++ b/Tusker/Screens/Main/MainSplitViewController.swift @@ -441,6 +441,10 @@ extension MainSplitViewController: TuskerRootViewController { searchViewController.searchController.searchBar.text = query searchViewController.resultsController.performSearch(query: query) } + + func presentPreferences(completion: (() -> Void)?) { + present(PreferencesNavigationController(mastodonController: mastodonController), animated: true, completion: completion) + } } extension MainSplitViewController: BackgroundableViewController { diff --git a/Tusker/Screens/Main/MainTabBarViewController.swift b/Tusker/Screens/Main/MainTabBarViewController.swift index 0e95755e..a1a004d8 100644 --- a/Tusker/Screens/Main/MainTabBarViewController.swift +++ b/Tusker/Screens/Main/MainTabBarViewController.swift @@ -262,6 +262,10 @@ extension MainTabBarViewController: TuskerRootViewController { exploreController.searchController.searchBar.text = query exploreController.resultsController.performSearch(query: query) } + + func presentPreferences(completion: (() -> Void)?) { + present(PreferencesNavigationController(mastodonController: mastodonController), animated: true, completion: completion) + } } extension MainTabBarViewController: BackgroundableViewController { diff --git a/Tusker/Screens/Main/TuskerRootViewController.swift b/Tusker/Screens/Main/TuskerRootViewController.swift index 80baafe9..d5f04bed 100644 --- a/Tusker/Screens/Main/TuskerRootViewController.swift +++ b/Tusker/Screens/Main/TuskerRootViewController.swift @@ -13,4 +13,5 @@ protocol TuskerRootViewController: UIViewController { func select(tab: MainTabBarViewController.Tab) func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? func performSearch(query: String) + func presentPreferences(completion: (() -> Void)?) }