From 1725868296e5a6f78e9f39e9eded6e4568bf5545 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 6 Jun 2019 12:56:13 -0700 Subject: [PATCH] [AltStore] Refresh all apps from My Apps tab --- AltStore/AppDelegate.swift | 2 +- AltStore/Apps/AppManager.swift | 14 ++- .../AuthenticationOperation.swift | 4 +- AltStore/Base.lproj/Main.storyboard | 9 +- AltStore/My Apps/MyAppsViewController.swift | 103 +++++++++++++++++- 5 files changed, 119 insertions(+), 13 deletions(-) diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index c065c3d4..e32b0f4b 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -77,7 +77,7 @@ extension AppDelegate ServerManager.shared.startDiscovering() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - AppManager.shared.refreshAllApps() { (result) in + AppManager.shared.refreshAllApps(presentingViewController: nil) { (result) in ServerManager.shared.stopDiscovering() let content = UNMutableNotificationContent() diff --git a/AltStore/Apps/AppManager.swift b/AltStore/Apps/AppManager.swift index fb6e8c70..4102c39b 100644 --- a/AltStore/Apps/AppManager.swift +++ b/AltStore/Apps/AppManager.swift @@ -191,9 +191,9 @@ extension AppManager } } - func refresh(_ app: InstalledApp, completionHandler: @escaping (Result) -> Void) + func refresh(_ app: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result) -> Void) { - self.refresh([app]) { (result) in + self.refresh([app], presentingViewController: presentingViewController) { (result) in do { guard let (_, result) = try result.get().first else { throw AppError.unknown } @@ -206,7 +206,7 @@ extension AppManager } } - func refreshAllApps(completionHandler: @escaping (Result<[String: Result], AppError>) -> Void) + func refreshAllApps(presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result], AppError>) -> Void) { DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in do @@ -215,7 +215,7 @@ extension AppManager fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)] let installedApps = try context.fetch(fetchRequest) - self.refresh(installedApps) { (result) in + self.refresh(installedApps, presentingViewController: presentingViewController) { (result) in context.perform { // keep context alive completionHandler(result) } @@ -228,7 +228,7 @@ extension AppManager } } - private func refresh(_ installedApps: T, completionHandler: @escaping (Result<[String: Result], AppError>) -> Void) where T.Element == InstalledApp + private func refresh(_ installedApps: T, presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result], AppError>) -> Void) where T.Element == InstalledApp { let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.RefreshApps") @@ -239,8 +239,10 @@ extension AppManager RSTEndBackgroundTask(backgroundTaskID) } + guard !ServerManager.shared.discoveredServers.isEmpty else { return finish(.failure(.noServersFound)) } + // Authenticate - self.authenticate(presentingViewController: nil) { (result) in + self.authenticate(presentingViewController: presentingViewController) { (result) in switch result { case .failure(let error): finish(.failure(.authentication(error))) diff --git a/AltStore/Authentication/AuthenticationOperation.swift b/AltStore/Authentication/AuthenticationOperation.swift index 70f4192b..58b68036 100644 --- a/AltStore/Authentication/AuthenticationOperation.swift +++ b/AltStore/Authentication/AuthenticationOperation.swift @@ -159,7 +159,9 @@ private extension AuthenticationOperation if self.navigationController.viewControllers.isEmpty { - self.navigationController.setViewControllers([viewController], animated: false) + guard presentingViewController.presentedViewController == nil else { return false } + + self.navigationController.setViewControllers([viewController], animated: false) presentingViewController.present(self.navigationController, animated: true, completion: nil) } else diff --git a/AltStore/Base.lproj/Main.storyboard b/AltStore/Base.lproj/Main.storyboard index b9e79b0a..813d1271 100644 --- a/AltStore/Base.lproj/Main.storyboard +++ b/AltStore/Base.lproj/Main.storyboard @@ -4,7 +4,6 @@ - @@ -73,7 +72,13 @@ - + + + + + + + diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index eaaecd10..020ef743 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -11,6 +11,8 @@ import Roxas class MyAppsViewController: UITableViewController { + private var refreshErrors = [String: Error]() + private lazy var dataSource = self.makeDataSource() private lazy var dateFormatter: DateFormatter = { @@ -25,6 +27,15 @@ class MyAppsViewController: UITableViewController super.viewDidLoad() self.tableView.dataSource = self.dataSource + + self.update() + } + + override func viewWillAppear(_ animated: Bool) + { + super.viewWillAppear(animated) + + self.update() } override func prepare(for segue: UIStoryboardSegue, sender: Any?) @@ -52,23 +63,94 @@ private extension MyAppsViewController let dataSource = RSTFetchedResultsTableViewDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) dataSource.proxy = self - dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in + dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in guard let app = installedApp.app else { return } cell.textLabel?.text = app.name let detailText = """ - Expires: \(self.dateFormatter.string(from: installedApp.expirationDate)) + Expires: \(self?.dateFormatter.string(from: installedApp.expirationDate) ?? "-") """ cell.detailTextLabel?.numberOfLines = 1 cell.detailTextLabel?.text = detailText cell.detailTextLabel?.textColor = .red + + if let _ = self?.refreshErrors[installedApp.bundleIdentifier] + { + cell.accessoryType = .detailButton + cell.tintColor = .red + } + else + { + cell.accessoryType = .none + cell.tintColor = nil + } } return dataSource } + + func update() + { + self.navigationItem.rightBarButtonItem?.isEnabled = !(self.dataSource.fetchedResultsController.fetchedObjects?.isEmpty ?? true) + + self.tableView.reloadData() + } +} + +private extension MyAppsViewController +{ + @IBAction func refreshAllApps(_ sender: UIBarButtonItem) + { + sender.isIndicatingActivity = true + + AppManager.shared.refreshAllApps(presentingViewController: self) { (result) in + DispatchQueue.main.async { + switch result + { + case .failure(let error): + let toastView = RSTToastView(text: error.localizedDescription, detailText: nil) + toastView.tintColor = .red + toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0) + + self.refreshErrors = [:] + + case .success(let results): + let failures = results.compactMapValues { $0.error } + + if failures.isEmpty + { + let toastView = RSTToastView(text: NSLocalizedString("Successfully refreshed apps!", comment: ""), detailText: nil) + toastView.tintColor = .altPurple + toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0) + } + else + { + let localizedText: String + if failures.count == 1 + { + localizedText = String(format: NSLocalizedString("Failed to refresh %@ app.", comment: ""), NSNumber(value: failures.count)) + } + else + { + localizedText = String(format: NSLocalizedString("Failed to refresh %@ apps.", comment: ""), NSNumber(value: failures.count)) + } + + let toastView = RSTToastView(text: localizedText, detailText: nil) + toastView.tintColor = .red + toastView.show(in: self.navigationController?.view ?? self.view, duration: 2.0) + } + + self.refreshErrors = failures + } + + sender.isIndicatingActivity = false + self.update() + } + } + } } extension MyAppsViewController @@ -101,7 +183,7 @@ extension MyAppsViewController toastView.activityIndicatorView.startAnimating() toastView.show(in: self.navigationController?.view ?? self.view) - AppManager.shared.refresh(installedApp) { (result) in + AppManager.shared.refresh(installedApp, presentingViewController: self) { (result) in do { let app = try result.get() @@ -111,6 +193,8 @@ extension MyAppsViewController let toastView = RSTToastView(text: "Refreshed \(installedApp.app.name)!", detailText: nil) toastView.tintColor = .altPurple toastView.show(in: self.navigationController?.view ?? self.view, duration: 2) + + self.update() } } catch @@ -119,6 +203,8 @@ extension MyAppsViewController let toastView = RSTToastView(text: "Failed to refresh \(installedApp.app.name)", detailText: error.localizedDescription) toastView.tintColor = .altPurple toastView.show(in: self.navigationController?.view ?? self.view, duration: 2) + + self.update() } } } @@ -130,4 +216,15 @@ extension MyAppsViewController override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { } + + override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) + { + let installedApp = self.dataSource.item(at: indexPath) + + guard let error = self.refreshErrors[installedApp.bundleIdentifier] else { return } + + let alertController = UIAlertController(title: "Failed to Refresh \(installedApp.app.name)", message: error.localizedDescription, preferredStyle: .alert) + alertController.addAction(.ok) + self.present(alertController, animated: true, completion: nil) + } }