mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-19 19:53:25 +01:00
[AltStore] Refresh all apps from My Apps tab
This commit is contained in:
@@ -77,7 +77,7 @@ extension AppDelegate
|
|||||||
ServerManager.shared.startDiscovering()
|
ServerManager.shared.startDiscovering()
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
AppManager.shared.refreshAllApps() { (result) in
|
AppManager.shared.refreshAllApps(presentingViewController: nil) { (result) in
|
||||||
ServerManager.shared.stopDiscovering()
|
ServerManager.shared.stopDiscovering()
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
|
|||||||
@@ -191,9 +191,9 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refresh(_ app: InstalledApp, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
|
func refresh(_ app: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
|
||||||
{
|
{
|
||||||
self.refresh([app]) { (result) in
|
self.refresh([app], presentingViewController: presentingViewController) { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let (_, result) = try result.get().first else { throw AppError.unknown }
|
guard let (_, result) = try result.get().first else { throw AppError.unknown }
|
||||||
@@ -206,7 +206,7 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshAllApps(completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], AppError>) -> Void)
|
func refreshAllApps(presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], AppError>) -> Void)
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
do
|
do
|
||||||
@@ -215,7 +215,7 @@ extension AppManager
|
|||||||
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)]
|
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)]
|
||||||
|
|
||||||
let installedApps = try context.fetch(fetchRequest)
|
let installedApps = try context.fetch(fetchRequest)
|
||||||
self.refresh(installedApps) { (result) in
|
self.refresh(installedApps, presentingViewController: presentingViewController) { (result) in
|
||||||
context.perform { // keep context alive
|
context.perform { // keep context alive
|
||||||
completionHandler(result)
|
completionHandler(result)
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refresh<T: Collection>(_ installedApps: T, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], AppError>) -> Void) where T.Element == InstalledApp
|
private func refresh<T: Collection>(_ installedApps: T, presentingViewController: UIViewController?, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], AppError>) -> Void) where T.Element == InstalledApp
|
||||||
{
|
{
|
||||||
let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.RefreshApps")
|
let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.RefreshApps")
|
||||||
|
|
||||||
@@ -239,8 +239,10 @@ extension AppManager
|
|||||||
RSTEndBackgroundTask(backgroundTaskID)
|
RSTEndBackgroundTask(backgroundTaskID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard !ServerManager.shared.discoveredServers.isEmpty else { return finish(.failure(.noServersFound)) }
|
||||||
|
|
||||||
// Authenticate
|
// Authenticate
|
||||||
self.authenticate(presentingViewController: nil) { (result) in
|
self.authenticate(presentingViewController: presentingViewController) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): finish(.failure(.authentication(error)))
|
case .failure(let error): finish(.failure(.authentication(error)))
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
if self.navigationController.viewControllers.isEmpty
|
if self.navigationController.viewControllers.isEmpty
|
||||||
{
|
{
|
||||||
|
guard presentingViewController.presentedViewController == nil else { return false }
|
||||||
|
|
||||||
self.navigationController.setViewControllers([viewController], animated: false)
|
self.navigationController.setViewControllers([viewController], animated: false)
|
||||||
presentingViewController.present(self.navigationController, animated: true, completion: nil)
|
presentingViewController.present(self.navigationController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@@ -73,7 +72,13 @@
|
|||||||
<outlet property="delegate" destination="Xf1-LZ-1vU" id="0YS-oa-D9d"/>
|
<outlet property="delegate" destination="Xf1-LZ-1vU" id="0YS-oa-D9d"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="My Apps" id="dz9-0e-LKa"/>
|
<navigationItem key="navigationItem" title="My Apps" id="dz9-0e-LKa">
|
||||||
|
<barButtonItem key="rightBarButtonItem" title="Refresh All" id="0Ke-yl-tAg">
|
||||||
|
<connections>
|
||||||
|
<action selector="refreshAllApps:" destination="Xf1-LZ-1vU" id="6op-Mf-HSD"/>
|
||||||
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
</navigationItem>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="nb5-5T-hHT" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="nb5-5T-hHT" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import Roxas
|
|||||||
|
|
||||||
class MyAppsViewController: UITableViewController
|
class MyAppsViewController: UITableViewController
|
||||||
{
|
{
|
||||||
|
private var refreshErrors = [String: Error]()
|
||||||
|
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
|
|
||||||
private lazy var dateFormatter: DateFormatter = {
|
private lazy var dateFormatter: DateFormatter = {
|
||||||
@@ -25,6 +27,15 @@ class MyAppsViewController: UITableViewController
|
|||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
self.tableView.dataSource = self.dataSource
|
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?)
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
@@ -52,23 +63,94 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
let dataSource = RSTFetchedResultsTableViewDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
let dataSource = RSTFetchedResultsTableViewDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||||
dataSource.proxy = self
|
dataSource.proxy = self
|
||||||
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
|
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
|
||||||
guard let app = installedApp.app else { return }
|
guard let app = installedApp.app else { return }
|
||||||
|
|
||||||
cell.textLabel?.text = app.name
|
cell.textLabel?.text = app.name
|
||||||
|
|
||||||
let detailText =
|
let detailText =
|
||||||
"""
|
"""
|
||||||
Expires: \(self.dateFormatter.string(from: installedApp.expirationDate))
|
Expires: \(self?.dateFormatter.string(from: installedApp.expirationDate) ?? "-")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cell.detailTextLabel?.numberOfLines = 1
|
cell.detailTextLabel?.numberOfLines = 1
|
||||||
cell.detailTextLabel?.text = detailText
|
cell.detailTextLabel?.text = detailText
|
||||||
cell.detailTextLabel?.textColor = .red
|
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
|
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
|
extension MyAppsViewController
|
||||||
@@ -101,7 +183,7 @@ extension MyAppsViewController
|
|||||||
toastView.activityIndicatorView.startAnimating()
|
toastView.activityIndicatorView.startAnimating()
|
||||||
toastView.show(in: self.navigationController?.view ?? self.view)
|
toastView.show(in: self.navigationController?.view ?? self.view)
|
||||||
|
|
||||||
AppManager.shared.refresh(installedApp) { (result) in
|
AppManager.shared.refresh(installedApp, presentingViewController: self) { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let app = try result.get()
|
let app = try result.get()
|
||||||
@@ -111,6 +193,8 @@ extension MyAppsViewController
|
|||||||
let toastView = RSTToastView(text: "Refreshed \(installedApp.app.name)!", detailText: nil)
|
let toastView = RSTToastView(text: "Refreshed \(installedApp.app.name)!", detailText: nil)
|
||||||
toastView.tintColor = .altPurple
|
toastView.tintColor = .altPurple
|
||||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2)
|
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2)
|
||||||
|
|
||||||
|
self.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -119,6 +203,8 @@ extension MyAppsViewController
|
|||||||
let toastView = RSTToastView(text: "Failed to refresh \(installedApp.app.name)", detailText: error.localizedDescription)
|
let toastView = RSTToastView(text: "Failed to refresh \(installedApp.app.name)", detailText: error.localizedDescription)
|
||||||
toastView.tintColor = .altPurple
|
toastView.tintColor = .altPurple
|
||||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 2)
|
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, 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user