mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Allows viewing unsupported updates from My Apps tab
When unsupported updates are available, the “No Updates Available” text becomes “Unsupported Updates Available”, and a button is revealed that will list all unsupported updates in an alert when tapped.
This commit is contained in:
@@ -42,6 +42,7 @@ final class MyAppsViewController: UICollectionViewController
|
||||
private lazy var updatesDataSource = self.makeUpdatesDataSource()
|
||||
private lazy var activeAppsDataSource = self.makeActiveAppsDataSource()
|
||||
private lazy var inactiveAppsDataSource = self.makeInactiveAppsDataSource()
|
||||
private lazy var hiddenUpdatesFetchedResultsController = self.makeHiddenUpdatesFetchedResultsController()
|
||||
|
||||
private var prototypeUpdateCell: UpdateCollectionViewCell!
|
||||
private var sideloadingProgressView: UIProgressView!
|
||||
@@ -80,6 +81,7 @@ final class MyAppsViewController: UICollectionViewController
|
||||
|
||||
// Allows us to intercept delegate callbacks.
|
||||
self.updatesDataSource.fetchedResultsController.delegate = self
|
||||
self.hiddenUpdatesFetchedResultsController.delegate = self
|
||||
|
||||
self.collectionView.dataSource = self.dataSource
|
||||
self.collectionView.prefetchDataSource = self.dataSource
|
||||
@@ -187,6 +189,19 @@ private extension MyAppsViewController
|
||||
cell.blurView.layer.cornerRadius = 20
|
||||
cell.blurView.layer.masksToBounds = true
|
||||
cell.blurView.backgroundColor = .altPrimary
|
||||
|
||||
cell.button.addTarget(self, action: #selector(MyAppsViewController.showHiddenUpdatesAlert(_:)), for: .primaryActionTriggered)
|
||||
|
||||
if let fetchedObjects = self.hiddenUpdatesFetchedResultsController.fetchedObjects, !fetchedObjects.isEmpty
|
||||
{
|
||||
cell.textLabel.text = NSLocalizedString("Unsupported Updates Available", comment: "")
|
||||
cell.button.isHidden = false
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.textLabel.text = NSLocalizedString("No Updates Available", comment: "")
|
||||
cell.button.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
return dynamicDataSource
|
||||
@@ -472,9 +487,37 @@ private extension MyAppsViewController
|
||||
return dataSource
|
||||
}
|
||||
|
||||
func makeHiddenUpdatesFetchedResultsController() -> NSFetchedResultsController<InstalledApp>
|
||||
{
|
||||
let fetchRequest = InstalledApp.updatesFetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.bundleIdentifier, ascending: true),
|
||||
NSSortDescriptor(keyPath: \InstalledApp.storeApp?.sourceIdentifier, ascending: true)] // Sorting doesn't matter as long as it's stable.
|
||||
|
||||
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: nil, cacheName: nil)
|
||||
return fetchedResultsController
|
||||
}
|
||||
|
||||
func updateDataSource()
|
||||
{
|
||||
do
|
||||
{
|
||||
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
|
||||
{
|
||||
try self.updatesDataSource.fetchedResultsController.performFetch()
|
||||
}
|
||||
|
||||
if self.hiddenUpdatesFetchedResultsController.fetchedObjects == nil
|
||||
{
|
||||
try self.hiddenUpdatesFetchedResultsController.performFetch()
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to fetch updates:", error)
|
||||
}
|
||||
|
||||
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||
{
|
||||
self.dataSource.predicate = nil
|
||||
|
||||
|
||||
@@ -487,6 +530,7 @@ private extension MyAppsViewController
|
||||
{
|
||||
do
|
||||
{
|
||||
try self.hiddenUpdatesFetchedResultsController.performFetch()
|
||||
try self.updatesDataSource.fetchedResultsController.performFetch()
|
||||
}
|
||||
catch
|
||||
@@ -935,6 +979,93 @@ private extension MyAppsViewController
|
||||
|
||||
cell.bannerView.iconImageView.isIndicatingActivity = false
|
||||
}
|
||||
|
||||
func removeAppExtensions(from application: ALTApplication, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
guard !application.appExtensions.isEmpty else { return completion(.success(())) }
|
||||
|
||||
let firstSentence: String
|
||||
|
||||
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
||||
{
|
||||
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
|
||||
}
|
||||
|
||||
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "")
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
||||
completion(.failure(OperationError.cancelled))
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
||||
completion(.success(()))
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
||||
do
|
||||
{
|
||||
for appExtension in application.appExtensions
|
||||
{
|
||||
try FileManager.default.removeItem(at: appExtension.fileURL)
|
||||
}
|
||||
|
||||
completion(.success(()))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completion(.failure(error))
|
||||
}
|
||||
})
|
||||
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func showHiddenUpdatesAlert(_ sender: UIButton)
|
||||
{
|
||||
guard let installedApps = self.hiddenUpdatesFetchedResultsController.fetchedObjects, !installedApps.isEmpty, self.updatesDataSource.itemCount == 0 else { return }
|
||||
|
||||
let numberOfHiddenUpdates = installedApps.count
|
||||
|
||||
let title = numberOfHiddenUpdates == 1 ? NSLocalizedString("Unsupported Update Available", comment: "") : String(format: NSLocalizedString("%@ Unsupported Updates Available", comment: ""), numberOfHiddenUpdates as NSNumber)
|
||||
var message = String(format: NSLocalizedString("These updates don't support iOS %@. Please update your device to the latest iOS version to install them.", comment: ""), ProcessInfo.processInfo.operatingSystemVersion.stringValue)
|
||||
message += "\n"
|
||||
|
||||
for installedApp in installedApps
|
||||
{
|
||||
guard let storeApp = installedApp.storeApp else { continue }
|
||||
|
||||
var title = storeApp.name
|
||||
if let appVersion = storeApp.latestAvailableVersion
|
||||
{
|
||||
title += " " + appVersion.version
|
||||
|
||||
var osVersion: String? = nil
|
||||
if let minOSVersion = appVersion.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion)
|
||||
{
|
||||
osVersion = String(format: NSLocalizedString("iOS %@ or later", comment: ""), minOSVersion.stringValue)
|
||||
}
|
||||
else if let maxOSVersion = appVersion.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion
|
||||
{
|
||||
osVersion = String(format: NSLocalizedString("iOS %@ or earlier", comment: ""), maxOSVersion.stringValue)
|
||||
}
|
||||
|
||||
if let osVersion
|
||||
{
|
||||
title += " (" + osVersion + ")"
|
||||
}
|
||||
}
|
||||
|
||||
message += "\n" + title
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private extension MyAppsViewController
|
||||
@@ -1286,12 +1417,6 @@ private extension MyAppsViewController
|
||||
@objc func didFetchSource(_ notification: Notification)
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
|
||||
{
|
||||
do { try self.updatesDataSource.fetchedResultsController.performFetch() }
|
||||
catch { print("Error fetching:", error) }
|
||||
}
|
||||
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
@@ -1966,38 +2091,54 @@ extension MyAppsViewController: NSFetchedResultsControllerDelegate
|
||||
// an accurate pre-update item count.
|
||||
self.collectionView.performBatchUpdates(nil, completion: nil)
|
||||
|
||||
self.updatesDataSource.controllerWillChangeContent(controller)
|
||||
if controller == self.updatesDataSource.fetchedResultsController
|
||||
{
|
||||
self.updatesDataSource.controllerWillChangeContent(controller)
|
||||
}
|
||||
}
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType)
|
||||
{
|
||||
guard controller == self.updatesDataSource.fetchedResultsController else { return }
|
||||
|
||||
self.updatesDataSource.controller(controller, didChange: sectionInfo, atSectionIndex: UInt(sectionIndex), for: type)
|
||||
}
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
|
||||
{
|
||||
guard controller == self.updatesDataSource.fetchedResultsController else { return }
|
||||
|
||||
self.updatesDataSource.controller(controller, didChange: anObject, at: indexPath, for: type, newIndexPath: newIndexPath)
|
||||
}
|
||||
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||
{
|
||||
let previousUpdateCount = self.collectionView.numberOfItems(inSection: Section.updates.rawValue)
|
||||
let updateCount = Int(self.updatesDataSource.itemCount)
|
||||
|
||||
if previousUpdateCount == 0 && updateCount > 0
|
||||
if controller == self.hiddenUpdatesFetchedResultsController && self.updatesDataSource.itemCount == 0
|
||||
{
|
||||
// Remove "No Updates Available" cell.
|
||||
let change = RSTCellContentChange(type: .delete, currentIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue), destinationIndexPath: nil)
|
||||
self.collectionView.add(change)
|
||||
// Reload noUpdates section whenever hiddenUpdatesFetchedResultsController changes (and there are no supported updates).
|
||||
// This ensures the cell correctly switches between "No Updates Available" and "Unsupported Updates Available".
|
||||
self.collectionView.reloadSections([Section.noUpdates.rawValue])
|
||||
}
|
||||
else if previousUpdateCount > 0 && updateCount == 0
|
||||
else if controller == self.updatesDataSource.fetchedResultsController
|
||||
{
|
||||
// Insert "No Updates Available" cell.
|
||||
let change = RSTCellContentChange(type: .insert, currentIndexPath: nil, destinationIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue))
|
||||
self.collectionView.add(change)
|
||||
let previousUpdateCount = self.collectionView.numberOfItems(inSection: Section.updates.rawValue)
|
||||
let updateCount = Int(self.updatesDataSource.itemCount)
|
||||
|
||||
if previousUpdateCount == 0 && updateCount > 0
|
||||
{
|
||||
// Remove "No Updates Available" cell.
|
||||
let change = RSTCellContentChange(type: .delete, currentIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue), destinationIndexPath: nil)
|
||||
self.collectionView.add(change)
|
||||
}
|
||||
else if previousUpdateCount > 0 && updateCount == 0
|
||||
{
|
||||
// Insert "No Updates Available" cell.
|
||||
let change = RSTCellContentChange(type: .insert, currentIndexPath: nil, destinationIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue))
|
||||
self.collectionView.add(change)
|
||||
}
|
||||
|
||||
self.updatesDataSource.controllerDidChangeContent(controller)
|
||||
}
|
||||
|
||||
self.updatesDataSource.controllerDidChangeContent(controller)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user