mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Fixes erroneously showing “Unsupported Updates Available” message
This commit is contained in:
@@ -347,6 +347,15 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchSources() async throws -> (Set<Source>, NSManagedObjectContext)
|
||||||
|
{
|
||||||
|
try await withCheckedThrowingContinuation { continuation in
|
||||||
|
self.fetchSources { result in
|
||||||
|
continuation.resume(with: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func add(@AsyncManaged _ source: Source, message: String? = nil, presentingViewController: UIViewController) async throws
|
func add(@AsyncManaged _ source: Source, message: String? = nil, presentingViewController: UIViewController) async throws
|
||||||
{
|
{
|
||||||
let (sourceName, sourceURL) = await $source.perform { ($0.name, $0.sourceURL) }
|
let (sourceName, sourceURL) = await $source.perform { ($0.name, $0.sourceURL) }
|
||||||
@@ -422,6 +431,7 @@ extension AppManager
|
|||||||
self.run([fetchSourceOperation], context: nil)
|
self.run([fetchSourceOperation], context: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, renamed: "fetchSources")
|
||||||
func fetchSources(completionHandler: @escaping (Result<(Set<Source>, NSManagedObjectContext), FetchSourcesError>) -> Void)
|
func fetchSources(completionHandler: @escaping (Result<(Set<Source>, NSManagedObjectContext), FetchSourcesError>) -> Void)
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
private lazy var updatesDataSource = self.makeUpdatesDataSource()
|
private lazy var updatesDataSource = self.makeUpdatesDataSource()
|
||||||
private lazy var activeAppsDataSource = self.makeActiveAppsDataSource()
|
private lazy var activeAppsDataSource = self.makeActiveAppsDataSource()
|
||||||
private lazy var inactiveAppsDataSource = self.makeInactiveAppsDataSource()
|
private lazy var inactiveAppsDataSource = self.makeInactiveAppsDataSource()
|
||||||
private lazy var hiddenUpdatesFetchedResultsController = self.makeHiddenUpdatesFetchedResultsController()
|
private lazy var unsupportedUpdates = Set<StoreApp>()
|
||||||
|
|
||||||
private var prototypeUpdateCell: UpdateCollectionViewCell!
|
private var prototypeUpdateCell: UpdateCollectionViewCell!
|
||||||
private var sideloadingProgressView: UIProgressView!
|
private var sideloadingProgressView: UIProgressView!
|
||||||
@@ -55,8 +55,10 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
private var sideloadingProgress: Progress?
|
private var sideloadingProgress: Progress?
|
||||||
private var dropDestinationIndexPath: IndexPath?
|
private var dropDestinationIndexPath: IndexPath?
|
||||||
private var isCheckingForUpdates = false
|
private var isCheckingForUpdates = false
|
||||||
|
private var didChangeActiveApps = false
|
||||||
|
|
||||||
private var _imagePickerInstalledApp: InstalledApp?
|
private var _imagePickerInstalledApp: InstalledApp?
|
||||||
|
private var _viewDidAppear = false
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
private var cachedUpdateSizes = [String: CGSize]()
|
private var cachedUpdateSizes = [String: CGSize]()
|
||||||
@@ -82,7 +84,7 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
|
|
||||||
// Allows us to intercept delegate callbacks.
|
// Allows us to intercept delegate callbacks.
|
||||||
self.updatesDataSource.fetchedResultsController.delegate = self
|
self.updatesDataSource.fetchedResultsController.delegate = self
|
||||||
self.hiddenUpdatesFetchedResultsController.delegate = self
|
self.activeAppsDataSource.fetchedResultsController.delegate = self
|
||||||
|
|
||||||
self.collectionView.dataSource = self.dataSource
|
self.collectionView.dataSource = self.dataSource
|
||||||
self.collectionView.prefetchDataSource = self.dataSource
|
self.collectionView.prefetchDataSource = self.dataSource
|
||||||
@@ -123,10 +125,18 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
self.updateDataSource()
|
self.updateDataSource()
|
||||||
|
self.update()
|
||||||
|
|
||||||
self.fetchAppIDs()
|
self.fetchAppIDs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool)
|
||||||
|
{
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
_viewDidAppear = true
|
||||||
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
{
|
{
|
||||||
guard let identifier = segue.identifier else { return }
|
guard let identifier = segue.identifier else { return }
|
||||||
@@ -193,7 +203,7 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
cell.button.addTarget(self, action: #selector(MyAppsViewController.showHiddenUpdatesAlert(_:)), for: .primaryActionTriggered)
|
cell.button.addTarget(self, action: #selector(MyAppsViewController.showHiddenUpdatesAlert(_:)), for: .primaryActionTriggered)
|
||||||
|
|
||||||
if let fetchedObjects = self.hiddenUpdatesFetchedResultsController.fetchedObjects, !fetchedObjects.isEmpty
|
if !self.unsupportedUpdates.isEmpty
|
||||||
{
|
{
|
||||||
cell.textLabel.text = NSLocalizedString("Unsupported Updates Available", comment: "")
|
cell.textLabel.text = NSLocalizedString("Unsupported Updates Available", comment: "")
|
||||||
cell.button.isHidden = false
|
cell.button.isHidden = false
|
||||||
@@ -485,16 +495,6 @@ private extension MyAppsViewController
|
|||||||
return dataSource
|
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()
|
func updateDataSource()
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
@@ -503,11 +503,6 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
try self.updatesDataSource.fetchedResultsController.performFetch()
|
try self.updatesDataSource.fetchedResultsController.performFetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.hiddenUpdatesFetchedResultsController.fetchedObjects == nil
|
|
||||||
{
|
|
||||||
try self.hiddenUpdatesFetchedResultsController.performFetch()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -526,15 +521,7 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
func update()
|
func update()
|
||||||
{
|
{
|
||||||
do
|
self.updateUnsupportedUpdates()
|
||||||
{
|
|
||||||
try self.hiddenUpdatesFetchedResultsController.performFetch()
|
|
||||||
try self.updatesDataSource.fetchedResultsController.performFetch()
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("[ALTLog] Failed to fetch updates:", error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.updatesDataSource.itemCount > 0
|
if self.updatesDataSource.itemCount > 0
|
||||||
{
|
{
|
||||||
@@ -546,6 +533,44 @@ private extension MyAppsViewController
|
|||||||
self.navigationController?.tabBarItem.badgeValue = nil
|
self.navigationController?.tabBarItem.badgeValue = nil
|
||||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reloading collection view when not visible can mess with cell margins.
|
||||||
|
guard self.isViewLoaded && self.view.window != nil else { return }
|
||||||
|
|
||||||
|
if #available(iOS 15, *)
|
||||||
|
{
|
||||||
|
// Don't reconfigureItems() while checking for updates to avoid incorrect UIRefreshControl animation.
|
||||||
|
// update() will be called again once we've finished checking.
|
||||||
|
if !self.isCheckingForUpdates
|
||||||
|
{
|
||||||
|
let indexPath = IndexPath(row: 0, section: Section.noUpdates.rawValue)
|
||||||
|
self.collectionView.reconfigureItems(at: [indexPath])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Might not work if already reloading collection view,
|
||||||
|
// but hopefully iOS 14 users won't notice...
|
||||||
|
self.collectionView.reloadSections(IndexSet([Section.noUpdates.rawValue]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUnsupportedUpdates()
|
||||||
|
{
|
||||||
|
// TIL includesPendingChanges does not apply to relationships, so we NEED to fetch InstalledApp to check isActive.
|
||||||
|
// let fetchRequest = StoreApp.fetchRequest()
|
||||||
|
// fetchRequest.includesPendingChanges = true // isActive might not be persisted to disk
|
||||||
|
|
||||||
|
let predicate = NSPredicate(format: "%K == YES AND %K != nil", #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp))
|
||||||
|
let activeSourceApps = InstalledApp.all(satisfying: predicate, in: DatabaseManager.shared.viewContext)
|
||||||
|
|
||||||
|
let unsupportedUpdates = activeSourceApps.compactMap { (installedApp) -> StoreApp? in
|
||||||
|
guard let storeApp = installedApp.storeApp, let appVersion = storeApp.latestAvailableVersion, !appVersion.isSupported else { return nil }
|
||||||
|
return storeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep StoreApp, not AppVersion, to prevent us accidentally holding onto AppVersions that may be deleted.
|
||||||
|
self.unsupportedUpdates = Set(unsupportedUpdates)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAppIDs()
|
func fetchAppIDs()
|
||||||
@@ -1034,18 +1059,16 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@objc func showHiddenUpdatesAlert(_ sender: UIButton)
|
@objc func showHiddenUpdatesAlert(_ sender: UIButton)
|
||||||
{
|
{
|
||||||
guard let installedApps = self.hiddenUpdatesFetchedResultsController.fetchedObjects, !installedApps.isEmpty, self.updatesDataSource.itemCount == 0 else { return }
|
guard !self.unsupportedUpdates.isEmpty else { return }
|
||||||
|
|
||||||
let numberOfHiddenUpdates = installedApps.count
|
let sortedHiddenUpdates = self.unsupportedUpdates.sorted(by: { $0.name.localizedStandardCompare($1.name) == .orderedAscending })
|
||||||
|
|
||||||
let title = numberOfHiddenUpdates == 1 ? NSLocalizedString("Unsupported Update Available", comment: "") : String(format: NSLocalizedString("%@ Unsupported Updates Available", comment: ""), numberOfHiddenUpdates as NSNumber)
|
let title = sortedHiddenUpdates.count == 1 ? NSLocalizedString("Unsupported Update Available", comment: "") : String(format: NSLocalizedString("%@ Unsupported Updates Available", comment: ""), sortedHiddenUpdates.count 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)
|
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"
|
message += "\n"
|
||||||
|
|
||||||
for installedApp in installedApps
|
for storeApp in sortedHiddenUpdates
|
||||||
{
|
{
|
||||||
guard let storeApp = installedApp.storeApp else { continue }
|
|
||||||
|
|
||||||
var title = storeApp.name
|
var title = storeApp.name
|
||||||
if let appVersion = storeApp.latestAvailableVersion
|
if let appVersion = storeApp.latestAvailableVersion
|
||||||
{
|
{
|
||||||
@@ -1453,56 +1476,71 @@ private extension MyAppsViewController
|
|||||||
guard !self.isCheckingForUpdates else { return }
|
guard !self.isCheckingForUpdates else { return }
|
||||||
self.isCheckingForUpdates = true
|
self.isCheckingForUpdates = true
|
||||||
|
|
||||||
AppManager.shared.fetchSources() { (result) in
|
Task<Void, Never> {
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
do
|
// async-let so the for-loop below runs first, ensuring we catch didFetchSourceNotification.
|
||||||
|
async let result = try await AppManager.shared.fetchSources()
|
||||||
|
|
||||||
|
if #available(iOS 15, *)
|
||||||
{
|
{
|
||||||
defer {
|
for await _ in NotificationCenter.default.notifications(named: AppManager.didFetchSourceNotification)
|
||||||
DispatchQueue.main.async {
|
{
|
||||||
self.isCheckingForUpdates = false
|
// Wait until _after_ didFetchSourceNotification
|
||||||
sender.endRefreshing()
|
// to prevent incorrect update() animations.
|
||||||
}
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, context) = try result.get()
|
|
||||||
try context.save()
|
|
||||||
}
|
}
|
||||||
catch let error as AppManager.FetchSourcesError
|
|
||||||
{
|
let (_, context) = try await result
|
||||||
try error.managedObjectContext?.save()
|
|
||||||
throw error
|
try await context.performAsync {
|
||||||
}
|
do
|
||||||
catch let mergeError as MergeError
|
{
|
||||||
{
|
try context.save()
|
||||||
guard let sourceID = mergeError.sourceID else { throw mergeError }
|
}
|
||||||
|
catch let error as AppManager.FetchSourcesError
|
||||||
let sanitizedError = (mergeError as NSError).sanitizedForSerialization()
|
{
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
try error.managedObjectContext?.save()
|
||||||
do
|
throw error
|
||||||
{
|
}
|
||||||
guard let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), sourceID), in: context) else { return }
|
catch let mergeError as MergeError
|
||||||
|
{
|
||||||
source.error = sanitizedError
|
guard let sourceID = mergeError.sourceID else { throw mergeError }
|
||||||
try context.save()
|
|
||||||
}
|
let sanitizedError = (mergeError as NSError).sanitizedForSerialization()
|
||||||
catch
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||||
{
|
do
|
||||||
print("[ALTLog] Failed to assign error \(sanitizedError.localizedErrorCode) to source \(sourceID).", error)
|
{
|
||||||
}
|
guard let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), sourceID), in: context) else { return }
|
||||||
|
|
||||||
|
source.error = sanitizedError
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to assign error \(sanitizedError.localizedErrorCode) to source \(sourceID).", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw mergeError
|
||||||
}
|
}
|
||||||
|
|
||||||
throw mergeError
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch let error as NSError
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
let toastView = ToastView(error: error.withLocalizedTitle(NSLocalizedString("Unable to Check for Updates", comment: "")))
|
||||||
let toastView = ToastView(error: error.withLocalizedTitle(NSLocalizedString("Unable to Check for Updates", comment: "")))
|
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
|
||||||
toastView.addTarget(nil, action: #selector(TabBarController.presentSources), for: .touchUpInside)
|
toastView.show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.isCheckingForUpdates = false
|
||||||
|
|
||||||
|
// Call update() _after_ setting isCheckingForUpdates to false so it will actually update collection view,
|
||||||
|
// but _before_ calling sender.endRefreshing() to avoid weird animation.
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
sender.endRefreshing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2141,43 +2179,64 @@ extension MyAppsViewController: NSFetchedResultsControllerDelegate
|
|||||||
{
|
{
|
||||||
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||||
{
|
{
|
||||||
// Responding to NSFetchedResultsController updates before the collection view has
|
guard let dataSource = self.dataSource(for: controller) else { return }
|
||||||
// been shown may throw exceptions because the collection view cannot accurately
|
|
||||||
// count the number of items before the update. However, if we manually call
|
|
||||||
// performBatchUpdates _before_ responding to updates, the collection view can get
|
|
||||||
// an accurate pre-update item count.
|
|
||||||
self.collectionView.performBatchUpdates(nil, completion: nil)
|
|
||||||
|
|
||||||
if controller == self.updatesDataSource.fetchedResultsController
|
switch dataSource
|
||||||
{
|
{
|
||||||
self.updatesDataSource.controllerWillChangeContent(controller)
|
case self.activeAppsDataSource: self.didChangeActiveApps = false
|
||||||
|
case self.updatesDataSource where !_viewDidAppear:
|
||||||
|
// Responding to NSFetchedResultsController updates before the collection view has
|
||||||
|
// been shown may throw exceptions because the collection view cannot accurately
|
||||||
|
// count the number of items before the update. However, if we manually call
|
||||||
|
// performBatchUpdates _before_ responding to updates, the collection view can get
|
||||||
|
// an accurate pre-update item count.
|
||||||
|
self.collectionView.performBatchUpdates(nil, completion: nil)
|
||||||
|
|
||||||
|
default: break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataSource.controllerWillChangeContent(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType)
|
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType)
|
||||||
{
|
{
|
||||||
guard controller == self.updatesDataSource.fetchedResultsController else { return }
|
guard let dataSource = self.dataSource(for: controller) else { return }
|
||||||
|
|
||||||
self.updatesDataSource.controller(controller, didChange: sectionInfo, atSectionIndex: UInt(sectionIndex), for: type)
|
dataSource.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?)
|
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
|
||||||
{
|
{
|
||||||
guard controller == self.updatesDataSource.fetchedResultsController else { return }
|
guard let dataSource = self.dataSource(for: controller) else { return }
|
||||||
|
|
||||||
self.updatesDataSource.controller(controller, didChange: anObject, at: indexPath, for: type, newIndexPath: newIndexPath)
|
switch dataSource
|
||||||
|
{
|
||||||
|
case self.activeAppsDataSource where type == .insert || type == .delete:
|
||||||
|
// Update unsupportedUpdates if there is insertion or deletion in active apps section.
|
||||||
|
self.didChangeActiveApps = true
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.controller(controller, didChange: anObject, at: indexPath, for: type, newIndexPath: newIndexPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||||
{
|
{
|
||||||
if controller == self.hiddenUpdatesFetchedResultsController && self.updatesDataSource.itemCount == 0
|
guard let dataSource = self.dataSource(for: controller) else { return }
|
||||||
{
|
|
||||||
// Reload noUpdates section whenever hiddenUpdatesFetchedResultsController changes (and there are no supported updates).
|
switch dataSource
|
||||||
// This ensures the cell correctly switches between "No Updates Available" and "Unsupported Updates Available".
|
|
||||||
self.collectionView.reloadSections([Section.noUpdates.rawValue])
|
|
||||||
}
|
|
||||||
else if controller == self.updatesDataSource.fetchedResultsController
|
|
||||||
{
|
{
|
||||||
|
case self.activeAppsDataSource:
|
||||||
|
guard self.didChangeActiveApps else { break }
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
// Update after dataSource.controllerDidChangeContent(),
|
||||||
|
// or else pre-iOS 15 users might crash due to reloadSections().
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
case self.updatesDataSource:
|
||||||
let previousUpdateCount = self.collectionView.numberOfItems(inSection: Section.updates.rawValue)
|
let previousUpdateCount = self.collectionView.numberOfItems(inSection: Section.updates.rawValue)
|
||||||
let updateCount = Int(self.updatesDataSource.itemCount)
|
let updateCount = Int(self.updatesDataSource.itemCount)
|
||||||
|
|
||||||
@@ -2192,9 +2251,24 @@ extension MyAppsViewController: NSFetchedResultsControllerDelegate
|
|||||||
// Insert "No Updates Available" cell.
|
// Insert "No Updates Available" cell.
|
||||||
let change = RSTCellContentChange(type: .insert, currentIndexPath: nil, destinationIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue))
|
let change = RSTCellContentChange(type: .insert, currentIndexPath: nil, destinationIndexPath: IndexPath(item: 0, section: Section.noUpdates.rawValue))
|
||||||
self.collectionView.add(change)
|
self.collectionView.add(change)
|
||||||
|
|
||||||
|
// Update unsupported updates _before_ calling controllerDidChangeContent()
|
||||||
|
self.updateUnsupportedUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updatesDataSource.controllerDidChangeContent(controller)
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.controllerDidChangeContent(controller)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dataSource(for controller: NSFetchedResultsController<NSFetchRequestResult>) -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>?
|
||||||
|
{
|
||||||
|
switch controller
|
||||||
|
{
|
||||||
|
case self.updatesDataSource.fetchedResultsController: return self.updatesDataSource
|
||||||
|
case self.activeAppsDataSource.fetchedResultsController: return self.activeAppsDataSource
|
||||||
|
default: return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,16 +197,6 @@ public extension InstalledApp
|
|||||||
return NSFetchRequest<InstalledApp>(entityName: "InstalledApp")
|
return NSFetchRequest<InstalledApp>(entityName: "InstalledApp")
|
||||||
}
|
}
|
||||||
|
|
||||||
class func updatesFetchRequest() -> NSFetchRequest<InstalledApp>
|
|
||||||
{
|
|
||||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
|
||||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K",
|
|
||||||
#keyPath(InstalledApp.isActive),
|
|
||||||
#keyPath(InstalledApp.storeApp),
|
|
||||||
#keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp._version))
|
|
||||||
return fetchRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
class func supportedUpdatesFetchRequest() -> NSFetchRequest<InstalledApp>
|
class func supportedUpdatesFetchRequest() -> NSFetchRequest<InstalledApp>
|
||||||
{
|
{
|
||||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
||||||
|
|||||||
Reference in New Issue
Block a user