// // LoginViewController.swift // Reader // // Created by Shadowfacts on 11/25/21. // import Foundation import UIKit import AuthenticationServices import Fervor @MainActor protocol LoginViewControllerDelegate: AnyObject { func didLogin(with controller: FervorController) } class LoginViewController: UIViewController { weak var delegate: LoginViewControllerDelegate? private var textField: UITextField! private var activityIndicator: UIActivityIndicatorView! private var authSession: ASWebAuthenticationSession? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground textField = UITextField() textField.translatesAutoresizingMaskIntoConstraints = false textField.borderStyle = .roundedRect textField.backgroundColor = .secondarySystemBackground textField.keyboardType = .URL textField.returnKeyType = .next textField.autocorrectionType = .no textField.autocapitalizationType = .none textField.placeholder = "example.com" textField.addTarget(self, action: #selector(doEnteredURL), for: .primaryActionTriggered) view.addSubview(textField) activityIndicator = UIActivityIndicatorView(style: .medium) activityIndicator.translatesAutoresizingMaskIntoConstraints = false view.addSubview(activityIndicator) NSLayoutConstraint.activate([ textField.centerYAnchor.constraint(equalTo: view.centerYAnchor), textField.centerXAnchor.constraint(equalTo: view.centerXAnchor), textField.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.75), activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), activityIndicator.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 8), ]) } @objc private func doEnteredURL() { Task { await enteredURL() } } @MainActor private func enteredURL() async { guard let text = textField.text, let components = URLComponents(string: text) else { let alert = UIAlertController(title: "Invalid URL", message: nil, preferredStyle: .alert) self.present(alert, animated: true) return } textField.isEnabled = false activityIndicator.startAnimating() let controller = await FervorController(instanceURL: components.url!, account: nil) let registration: ClientRegistration do { registration = try await controller.register() } catch { let alert = UIAlertController(title: "Unable to register client", message: error.localizedDescription, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) present(alert, animated: true) textField.isEnabled = true activityIndicator.stopAnimating() return } var authorizeComponents = components authorizeComponents.path = "/oauth/authorize" authorizeComponents.queryItems = [ URLQueryItem(name: "response_type", value: "code"), URLQueryItem(name: "client_id", value: registration.clientID), URLQueryItem(name: "redirect_uri", value: FervorController.oauthRedirectURI.absoluteString), ] authSession = ASWebAuthenticationSession(url: authorizeComponents.url!, callbackURLScheme: "frenzy") { (callbackURL, error) in let components = URLComponents(url: callbackURL!, resolvingAgainstBaseURL: false) guard let codeItem = components?.queryItems?.first(where: { $0.name == "code" }), let codeValue = codeItem.value else { Task { @MainActor in let alert = UIAlertController(title: "Unable to retrieve authorization code", message: error?.localizedDescription ?? "Unknown Error", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) self.present(alert, animated: true) self.textField.isEnabled = true self.activityIndicator.stopAnimating() } return } Task { @MainActor in do { try await controller.getToken(authCode: codeValue) } catch { let alert = UIAlertController(title: "Unable to retrieve access token", message: error.localizedDescription, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) self.present(alert, animated: true) self.textField.isEnabled = true self.activityIndicator.stopAnimating() return } self.delegate?.didLogin(with: controller) } } self.authSession!.presentationContextProvider = self self.authSession!.start() } } extension LoginViewController: ASWebAuthenticationPresentationContextProviding { func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { return self.view.window! } }