// // PreferencesHostingController.swift // Tusker // // Created by Shadowfacts on 11/17/19. // Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit import SwiftUI import UserAccounts import SafariServices import AuthenticationServices import Pachyderm class PreferencesNavigationController: UINavigationController { private let mastodonController: MastodonController private var isSwitchingAccounts = false init(mastodonController: MastodonController) { self.mastodonController = mastodonController let view = PreferencesView(mastodonController: mastodonController) let hostingController = UIHostingController(rootView: view) super.init(rootViewController: hostingController) hostingController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed)) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) NotificationCenter.default.addObserver(self, selector: #selector(showAddAccount), name: .addAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(activateAccount(_:)), name: .activateAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userLoggedOut), name: .userLoggedOut, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(showMastodonSettings), name: .showMastodonSettings, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reLogInToAccount), name: .reLogInRequired, object: nil) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if !isSwitchingAccounts { // workaround for onDisappear not being called when a modally presented UIHostingController is dismissed NotificationCenter.default.post(name: .preferencesChanged, object: nil) } } @objc func donePressed() { dismiss(animated: true) } @objc func showAddAccount() { let onboardingController = OnboardingViewController() onboardingController.onboardingDelegate = self onboardingController.instanceSelector.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelAddAccount)) show(onboardingController, sender: self) } @objc func cancelAddAccount() { dismiss(animated: true) // dismisses instance selector } @objc func activateAccount(_ notification: Foundation.Notification) { // TODO: this is a temporary measure // when switching accounts shortly after adding a new one, there is an old instance of PreferncesNavigationController still around // which tries to handle the notification but is unable to because it no longer is in a window (and therefore doesn't have a scene delegate) // the propper fix would be to figure out what's leaking instances of this class guard let windowScene = self.view.window?.windowScene else { return } let account = notification.userInfo!["account"] as! UserAccountInfo if let sceneDelegate = windowScene.delegate as? MainSceneDelegate { isSwitchingAccounts = true dismiss(animated: true) { // dismiss preferences sceneDelegate.activateAccount(account, animated: true) } } else { UIApplication.shared.requestSceneSessionActivation(nil, userActivity: UserActivityManager.mainSceneActivity(accountID: account.id), options: nil) } } @objc func userLoggedOut() { guard let windowScene = self.view.window?.windowScene else { return } if let sceneDelegate = windowScene.delegate as? MainSceneDelegate { isSwitchingAccounts = true dismiss(animated: true) { // dismiss preferences sceneDelegate.logoutCurrent() } } else { LogoutService(accountInfo: UserAccountsManager.shared.getMostRecentAccount()!).run() let accountID = UserAccountsManager.shared.getMostRecentAccount()?.id UIApplication.shared.requestSceneSessionActivation(nil, userActivity: UserActivityManager.mainSceneActivity(accountID: accountID), options: nil) UIApplication.shared.requestSceneSessionDestruction(windowScene.session, options: nil) } } @objc private func showMastodonSettings() { var components = URLComponents(url: mastodonController.accountInfo!.instanceURL, resolvingAgainstBaseURL: false)! components.path = "/auth/edit" let vc = SFSafariViewController(url: components.url!) present(vc, animated: true) } @objc private func reLogInToAccount(_ notification: Foundation.Notification) { guard let account = notification.object as? UserAccountInfo else { return } Task { do { let service = GetAuthorizationTokenService(instanceURL: account.instanceURL, clientID: account.clientID, presentationContextProvider: self) let code = try await service.run() let mastodonController = MastodonController.getForAccount(account) let token = try await mastodonController.authorize(authorizationCode: code) UserAccountsManager.shared.updateAccessToken(account, token: token, scopes: MastodonController.oauthScopes.map(\.rawValue)) // try to revoke the old token try? await Client(baseURL: account.instanceURL, accessToken: account.accessToken, clientID: account.clientID, clientSecret: account.clientSecret).revokeAccessToken() } catch { let alert = UIAlertController(title: "Error Updating Permissions", message: error.localizedDescription, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) self.present(alert, animated: true) } } } } extension PreferencesNavigationController: OnboardingViewControllerDelegate { func didFinishOnboarding(account: UserAccountInfo) { guard let windowScene = self.view.window?.windowScene else { return } self.dismiss(animated: true) { // dismiss instance selector self.dismiss(animated: true) { // dismiss preferences if let sceneDelegate = windowScene.delegate as? MainSceneDelegate { sceneDelegate.activateAccount(account, animated: false) } else { UIApplication.shared.requestSceneSessionActivation(nil, userActivity: UserActivityManager.mainSceneActivity(accountID: account.id), options: nil) } } } } } extension PreferencesNavigationController: ASWebAuthenticationPresentationContextProviding { func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { view.window! } } extension Foundation.Notification.Name { static let showMastodonSettings = Notification.Name("Tusker.showMastodonSettings") static let reLogInRequired = Notification.Name("Tusker.reLogInRequired") }