diff --git a/AltStore/App Detail/AppContentViewController.swift b/AltStore/App Detail/AppContentViewController.swift index b6dc5ba8..bd6c2a6e 100644 --- a/AltStore/App Detail/AppContentViewController.swift +++ b/AltStore/App Detail/AppContentViewController.swift @@ -81,7 +81,7 @@ class AppContentViewController: UITableViewController self.subtitleLabel.text = self.app.subtitle self.descriptionTextView.text = self.app.localizedDescription - if let version = self.app.latestVersion + if let version = self.app.latestAvailableVersion { self.versionDescriptionTextView.text = version.localizedDescription self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.version) diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift index a68aec57..4d3ed3f8 100644 --- a/AltStore/App Detail/AppViewController.swift +++ b/AltStore/App Detail/AppViewController.swift @@ -384,7 +384,7 @@ private extension AppViewController button.progress = progress } - if let versionDate = self.app.latestVersion?.date, versionDate > Date() + if let versionDate = self.app.latestAvailableVersion?.date, versionDate > Date() { self.bannerView.button.countdownDate = versionDate self.navigationBarDownloadButton.countdownDate = versionDate diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index 85b8c861..fb39fed7 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -384,7 +384,7 @@ private extension AppDelegate for update in updates { guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue } - guard let storeApp = update.storeApp, let version = storeApp.version else { continue } + guard let storeApp = update.storeApp, let version = storeApp.latestSupportedVersion else { continue } let content = UNMutableNotificationContent() content.title = NSLocalizedString("New Update Available", comment: "") diff --git a/AltStore/Browse/BrowseViewController.swift b/AltStore/Browse/BrowseViewController.swift index 27e7d283..548e3a47 100644 --- a/AltStore/Browse/BrowseViewController.swift +++ b/AltStore/Browse/BrowseViewController.swift @@ -113,9 +113,9 @@ private extension BrowseViewController let progress = AppManager.shared.installationProgress(for: app) cell.bannerView.button.progress = progress - if let versionDate = app.latestVersion?.date, versionDate > Date() + if let versionDate = app.latestSupportedVersion?.date, versionDate > Date() { - cell.bannerView.button.countdownDate = app.versionDate + cell.bannerView.button.countdownDate = versionDate } else { diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index dc2f8b5b..cf48b470 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -186,7 +186,7 @@ private extension MyAppsViewController func makeUpdatesDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource { let fetchRequest = InstalledApp.updatesFetchRequest() - fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestVersion?.date, ascending: true), + fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestSupportedVersion?.date, ascending: false), NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)] fetchRequest.returnsObjectsAsFaults = false @@ -195,21 +195,21 @@ private extension MyAppsViewController dataSource.cellIdentifierHandler = { _ in "UpdateCell" } dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in guard let self = self else { return } - guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return } + guard let app = installedApp.storeApp, let latestSupportedVersion = app.latestSupportedVersion else { return } let cell = cell as! UpdateCollectionViewCell cell.layoutMargins.left = self.view.layoutMargins.left cell.layoutMargins.right = self.view.layoutMargins.right cell.tintColor = app.tintColor ?? .altPrimary - cell.versionDescriptionTextView.text = app.versionDescription + cell.versionDescriptionTextView.text = latestSupportedVersion.localizedDescription cell.bannerView.iconImageView.image = nil cell.bannerView.iconImageView.isIndicatingActivity = true cell.bannerView.configure(for: app) - let versionDate = Date().relativeDateString(since: latestVersion.date, dateFormatter: self.dateFormatter) + let versionDate = Date().relativeDateString(since: latestSupportedVersion.date, dateFormatter: self.dateFormatter) cell.bannerView.subtitleLabel.text = versionDate let appName: String @@ -223,7 +223,7 @@ private extension MyAppsViewController appName = app.name } - cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestVersion.version, versionDate) + cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestSupportedVersion.version, versionDate) cell.bannerView.button.isIndicatingActivity = false cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered) diff --git a/AltStore/News/NewsViewController.swift b/AltStore/News/NewsViewController.swift index e41dcad0..44e8009a 100644 --- a/AltStore/News/NewsViewController.swift +++ b/AltStore/News/NewsViewController.swift @@ -392,9 +392,9 @@ extension NewsViewController let progress = AppManager.shared.installationProgress(for: storeApp) footerView.bannerView.button.progress = progress - if let versionDate = storeApp.latestVersion?.date, versionDate > Date() + if let versionDate = storeApp.latestSupportedVersion?.date, versionDate > Date() { - footerView.bannerView.button.countdownDate = storeApp.versionDate + footerView.bannerView.button.countdownDate = versionDate } else { diff --git a/AltStoreCore/Model/AppVersion.swift b/AltStoreCore/Model/AppVersion.swift index 2bbde4a6..dc0ffe80 100644 --- a/AltStoreCore/Model/AppVersion.swift +++ b/AltStoreCore/Model/AppVersion.swift @@ -40,7 +40,7 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable /* Relationships */ @NSManaged public private(set) var app: StoreApp? - @NSManaged public private(set) var latestVersionApp: StoreApp? + @NSManaged @objc(latestVersionApp) public internal(set) var latestSupportedVersionApp: StoreApp? private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { @@ -113,4 +113,8 @@ public extension AppVersion return appVersion } + + var isSupported: Bool { + return true + } } diff --git a/AltStoreCore/Model/DatabaseManager.swift b/AltStoreCore/Model/DatabaseManager.swift index 3d443a62..83f7e6d2 100644 --- a/AltStoreCore/Model/DatabaseManager.swift +++ b/AltStoreCore/Model/DatabaseManager.swift @@ -232,7 +232,7 @@ private extension DatabaseManager else { storeApp = StoreApp.makeAltStoreApp(in: context) - storeApp.latestVersion?.version = localApp.version + storeApp.latestSupportedVersion?.version = localApp.version storeApp.source = altStoreSource } diff --git a/AltStoreCore/Model/InstalledApp.swift b/AltStoreCore/Model/InstalledApp.swift index cb5e2bdd..41ff1475 100644 --- a/AltStoreCore/Model/InstalledApp.swift +++ b/AltStoreCore/Model/InstalledApp.swift @@ -146,7 +146,7 @@ public extension InstalledApp { let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K", - #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestVersion.version)) + #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestSupportedVersion.version)) return fetchRequest } diff --git a/AltStoreCore/Model/MergePolicy.swift b/AltStoreCore/Model/MergePolicy.swift index 08038988..a963a1fa 100644 --- a/AltStoreCore/Model/MergePolicy.swift +++ b/AltStoreCore/Model/MergePolicy.swift @@ -44,7 +44,7 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy let conflictingAppVersions = conflict.conflictingObjects.lazy.compactMap { $0 as? AppVersion } // Primary AppVersion == AppVersion whose latestVersionApp.latestVersion points back to itself. - if let primaryAppVersion = conflictingAppVersions.first(where: { $0.latestVersionApp?.latestVersion == $0 }), + if let primaryAppVersion = conflictingAppVersions.first(where: { $0.latestSupportedVersionApp?.latestSupportedVersion == $0 }), let secondaryAppVersion = conflictingAppVersions.first(where: { $0 != primaryAppVersion }) { secondaryAppVersion.managedObjectContext?.delete(secondaryAppVersion) diff --git a/AltStoreCore/Model/Migrations/Policies/StoreApp10ToStoreApp11Policy.swift b/AltStoreCore/Model/Migrations/Policies/StoreApp10ToStoreApp11Policy.swift index ead50098..7ca75c60 100644 --- a/AltStoreCore/Model/Migrations/Policies/StoreApp10ToStoreApp11Policy.swift +++ b/AltStoreCore/Model/Migrations/Policies/StoreApp10ToStoreApp11Policy.swift @@ -48,7 +48,7 @@ fileprivate extension NSManagedObject func setStoreAppLatestVersion(_ appVersion: NSManagedObject) { - self.setValue(appVersion, forKey: #keyPath(StoreApp.latestVersion)) + self.setValue(appVersion, forKey: #keyPath(StoreApp.latestSupportedVersion)) let versions = NSOrderedSet(array: [appVersion]) self.setValue(versions, forKey: #keyPath(StoreApp._versions)) diff --git a/AltStoreCore/Model/StoreApp.swift b/AltStoreCore/Model/StoreApp.swift index f0702ab9..265d44ad 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -78,7 +78,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable @NSManaged @objc(source) public var _source: Source? @NSManaged @objc(permissions) public var _permissions: NSOrderedSet - @NSManaged public private(set) var latestVersion: AppVersion? + @NSManaged @objc(latestVersion) public private(set) var latestSupportedVersion: AppVersion? @NSManaged @objc(versions) public private(set) var _versions: NSOrderedSet @NSManaged public private(set) var loggedErrors: NSSet /* Set */ // Use NSSet to avoid eagerly fetching values. @@ -227,16 +227,30 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable } } -private extension StoreApp +internal extension StoreApp { func setVersions(_ versions: [AppVersion]) { - guard let latestVersion = versions.first else { preconditionFailure("StoreApp must have at least one AppVersion.") } - - self.latestVersion = latestVersion self._versions = NSOrderedSet(array: versions) + let latestSupportedVersion = versions.first(where: { $0.isSupported }) + self.latestSupportedVersion = latestSupportedVersion + + for case let version as AppVersion in self._versions + { + if version == latestSupportedVersion + { + version.latestSupportedVersionApp = self + } + else + { + // Ensure we replace any previous relationship when merging. + version.latestSupportedVersionApp = nil + } + } + // Preserve backwards compatibility by assigning legacy property values. + guard let latestVersion = versions.first else { preconditionFailure("StoreApp must have at least one AppVersion.") } self._version = latestVersion.version self._versionDate = latestVersion.date self._versionDescription = latestVersion.localizedDescription @@ -247,6 +261,10 @@ private extension StoreApp public extension StoreApp { + var latestAvailableVersion: AppVersion? { + return self._versions.firstObject as? AppVersion + } + @nonobjc class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "StoreApp")