Tusker/Tusker/Screens/Preferences/PreferencesNavigationContro...

213 lines
9.8 KiB
Swift

//
// 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
// TODO: replace this with NavigationStack and path once we target iOS 16
class PreferencesNavigationState: ObservableObject {
@Published var showNotificationPreferences = false
}
class PreferencesNavigationController: UINavigationController {
private let mastodonController: MastodonController
let navigationState: PreferencesNavigationState
private var isSwitchingAccounts = false
init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
let navigationState = PreferencesNavigationState()
self.navigationState = navigationState
let view = PreferencesView(mastodonController: mastodonController, navigationState: navigationState)
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
}
let dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = .black.withAlphaComponent(0.1)
let blur = UIBlurEffect(style: .prominent)
let blurView = UIVisualEffectView(effect: blur)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.layer.cornerRadius = 15
blurView.layer.masksToBounds = true
let spinner = UIActivityIndicatorView(style: .large)
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.startAnimating()
blurView.contentView.addSubview(spinner)
dimmingView.addSubview(blurView)
view.addSubview(dimmingView)
NSLayoutConstraint.activate([
dimmingView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
dimmingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
dimmingView.topAnchor.constraint(equalTo: view.topAnchor),
dimmingView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
blurView.widthAnchor.constraint(equalToConstant: 100),
blurView.heightAnchor.constraint(equalToConstant: 100),
blurView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
blurView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
spinner.centerXAnchor.constraint(equalTo: blurView.contentView.centerXAnchor),
spinner.centerYAnchor.constraint(equalTo: blurView.contentView.centerYAnchor),
])
dimmingView.layer.opacity = 0
blurView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) {
dimmingView.layer.opacity = 1
blurView.transform = .identity
}
Task {
do {
let mastodonController = MastodonController.getForAccount(account)
let (clientID, clientSecret) = try await mastodonController.registerApp(reregister: true)
let service = GetAuthorizationTokenService(instanceURL: account.instanceURL, clientID: clientID, presentationContextProvider: self)
let code = try await service.run()
let token = try await mastodonController.authorize(authorizationCode: code)
UserAccountsManager.shared.updateCredentials(account, clientID: clientID, clientSecret: clientSecret, accessToken: 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)
}
dimmingView.removeFromSuperview()
}
}
}
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")
}