diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index f2b40e50..e926a1a9 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -54,11 +54,13 @@ extension AppDelegate { static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification") static let importAppDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.ImportAppDeepLinkNotification") + static let addSourceDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.AddSourceDeepLinkNotification") static let appBackupDidFinish = Notification.Name("com.rileytestut.AltStore.AppBackupDidFinish") static let importAppDeepLinkURLKey = "fileURL" static let appBackupResultKey = "result" + static let addSourceDeepLinkURLKey = "sourceURL" } @UIApplicationMain @@ -183,6 +185,16 @@ private extension AppDelegate } return true + + case "source": + let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:] + guard let sourceURLString = queryItems["url"], let sourceURL = URL(string: sourceURLString) else { return false } + + DispatchQueue.main.async { + NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL]) + } + + return true default: return false } diff --git a/AltStore/Sources/SourcesViewController.swift b/AltStore/Sources/SourcesViewController.swift index f38de96f..21950e9a 100644 --- a/AltStore/Sources/SourcesViewController.swift +++ b/AltStore/Sources/SourcesViewController.swift @@ -13,6 +13,13 @@ import Roxas class SourcesViewController: UICollectionViewController { + var deepLinkSourceURL: URL? { + didSet { + guard let sourceURL = self.deepLinkSourceURL else { return } + self.addSource(url: sourceURL) + } + } + private lazy var dataSource = self.makeDataSource() override func viewDidLoad() @@ -21,6 +28,27 @@ class SourcesViewController: UICollectionViewController self.collectionView.dataSource = self.dataSource } + + override func viewWillAppear(_ animated: Bool) + { + super.viewWillAppear(animated) + + if self.deepLinkSourceURL != nil + { + self.navigationItem.leftBarButtonItem?.isIndicatingActivity = true + } + } + + override func viewDidAppear(_ animated: Bool) + { + super.viewDidAppear(animated) + + if let sourceURL = self.deepLinkSourceURL + { + self.addSource(url: sourceURL) + self.deepLinkSourceURL = nil + } + } } private extension SourcesViewController @@ -67,26 +95,6 @@ private extension SourcesViewController { @IBAction func addSource() { - func addSource(url: URL) - { - AppManager.shared.fetchSource(sourceURL: url) { (result) in - do - { - let source = try result.get() - try source.managedObjectContext?.save() - } - catch let error as NSError - { - let error = error.withLocalizedFailure(NSLocalizedString("Could not add source.", comment: "")) - - DispatchQueue.main.async { - let toastView = ToastView(error: error) - toastView.show(in: self) - } - } - } - } - let alertController = UIAlertController(title: NSLocalizedString("Add Source", comment: ""), message: nil, preferredStyle: .alert) alertController.addTextField { (textField) in textField.placeholder = "https://apps.altstore.io" @@ -95,14 +103,77 @@ private extension SourcesViewController alertController.addAction(.cancel) alertController.addAction(UIAlertAction(title: NSLocalizedString("Add", comment: ""), style: .default) { (action) in guard let text = alertController.textFields![0].text, let sourceURL = URL(string: text) else { return } - addSource(url: sourceURL) + self.addSource(url: sourceURL) }) self.present(alertController, animated: true, completion: nil) } + + func addSource(url: URL) + { + guard self.view.window != nil else { return } + + self.navigationItem.leftBarButtonItem?.isIndicatingActivity = true + + func finish(error: Error?) + { + DispatchQueue.main.async { + if let error = error + { + self.present(error) + } + + self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false + } + } + + AppManager.shared.fetchSource(sourceURL: url) { (result) in + do + { + let source = try result.get() + let sourceName = source.name + let managedObjectContext = source.managedObjectContext + + DispatchQueue.main.async { + let alertController = UIAlertController(title: String(format: NSLocalizedString("Would you like to add the source “%@”?", comment: ""), sourceName), + message: NSLocalizedString("Sources control what apps appear in AltStore. Make sure to only add sources that you trust.", comment: ""), preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { _ in + finish(error: nil) + }) + alertController.addAction(UIAlertAction(title: UIAlertAction.ok.title, style: UIAlertAction.ok.style) { _ in + managedObjectContext?.perform { + do + { + try managedObjectContext?.save() + finish(error: nil) + } + catch + { + finish(error: error) + } + } + }) + self.present(alertController, animated: true, completion: nil) + } + } + catch + { + finish(error: error) + } + } + } func present(_ error: Error) { + if let transitionCoordinator = self.transitionCoordinator + { + transitionCoordinator.animate(alongsideTransition: nil) { _ in + self.present(error) + } + + return + } + let nsError = error as NSError let message = nsError.userInfo[NSDebugDescriptionErrorKey] as? String ?? nsError.localizedRecoverySuggestion diff --git a/AltStore/TabBarController.swift b/AltStore/TabBarController.swift index cb200254..5765e4b9 100644 --- a/AltStore/TabBarController.swift +++ b/AltStore/TabBarController.swift @@ -21,12 +21,52 @@ extension TabBarController class TabBarController: UITabBarController { + private var initialSegue: (identifier: String, sender: Any?)? + + private var _viewDidAppear = false + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.presentSources(_:)), name: AppDelegate.addSourceDeepLinkNotification, object: nil) + } + + override func viewDidAppear(_ animated: Bool) + { + super.viewDidAppear(animated) + + _viewDidAppear = true + + if let (identifier, sender) = self.initialSegue + { + self.initialSegue = nil + self.performSegue(withIdentifier: identifier, sender: sender) + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) + { + guard segue.identifier == "presentSources", + let notification = sender as? Notification, + let sourceURL = notification.userInfo?[AppDelegate.addSourceDeepLinkURLKey] as? URL + else { return } + + let navigationController = segue.destination as! UINavigationController + let sourcesViewController = navigationController.viewControllers.first as! SourcesViewController + sourcesViewController.deepLinkSourceURL = sourceURL + } + + override func performSegue(withIdentifier identifier: String, sender: Any?) + { + guard _viewDidAppear else { + self.initialSegue = (identifier, sender) + return + } + + super.performSegue(withIdentifier: identifier, sender: sender) } } @@ -34,6 +74,31 @@ extension TabBarController { @objc func presentSources(_ sender: Any) { + if let presentedViewController = self.presentedViewController + { + if let navigationController = presentedViewController as? UINavigationController, + let sourcesViewController = navigationController.viewControllers.first as? SourcesViewController + { + if let notification = (sender as? Notification), + let sourceURL = notification.userInfo?[AppDelegate.addSourceDeepLinkURLKey] as? URL + { + sourcesViewController.deepLinkSourceURL = sourceURL + } + else + { + // Don't dismiss SourcesViewController if it's already presented. + } + } + else + { + presentedViewController.dismiss(animated: true) { + self.presentSources(sender) + } + } + + return + } + self.performSegue(withIdentifier: "presentSources", sender: sender) } }