diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard index 385e0bb5..e6db446b 100644 --- a/AltStore/Settings/Settings.storyboard +++ b/AltStore/Settings/Settings.storyboard @@ -21,7 +21,7 @@ - + @@ -526,7 +526,7 @@ - + @@ -559,10 +559,10 @@ - + - + @@ -591,10 +591,10 @@ - + - + @@ -635,10 +635,10 @@ - + - + @@ -679,10 +679,10 @@ - + - + @@ -723,10 +723,10 @@ - + - + @@ -763,10 +763,10 @@ - + - + @@ -796,10 +796,10 @@ - + - + @@ -832,10 +832,10 @@ - + - + @@ -865,10 +865,10 @@ - + - + @@ -898,10 +898,10 @@ - + - + @@ -931,10 +931,10 @@ - + - + @@ -959,6 +959,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -982,6 +1016,7 @@ + diff --git a/AltStore/Settings/SettingsViewController.swift b/AltStore/Settings/SettingsViewController.swift index a1ed4943..067cf94f 100644 --- a/AltStore/Settings/SettingsViewController.swift +++ b/AltStore/Settings/SettingsViewController.swift @@ -71,9 +71,9 @@ extension SettingsViewController case refreshSideJITServer case resetPairingFile case anisetteServers - case advancedSettings - case responseCaching + case betaUpdates +// case advancedSettings } } @@ -93,6 +93,7 @@ final class SettingsViewController: UITableViewController @IBOutlet private var backgroundRefreshSwitch: UISwitch! @IBOutlet private var noIdleTimeoutSwitch: UISwitch! @IBOutlet private var disableAppLimitSwitch: UISwitch! + @IBOutlet private var isBetaUpdatesEnabled: UISwitch! @IBOutlet private var refreshSideJITServer: UILabel! @IBOutlet private var disableResponseCachingSwitch: UISwitch! @@ -138,8 +139,7 @@ final class SettingsViewController: UITableViewController // Only show build version for BETA builds. let localizedVersion = if let bundleVersion = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String { "\(installedApp.version) (\(bundleVersion))" - } - else { + } else { installedApp.localizedVersion } #else @@ -250,7 +250,8 @@ private extension SettingsViewController self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled self.disableAppLimitSwitch.isOn = UserDefaults.standard.isAppLimitDisabled self.disableResponseCachingSwitch.isOn = UserDefaults.standard.responseCachingDisabled - + self.isBetaUpdatesEnabled.isOn = UserDefaults.standard.isBetaUpdatesEnabled + if self.isViewLoaded { self.tableView.reloadData() @@ -428,6 +429,11 @@ private extension SettingsViewController } } + @IBAction func toggleEnableBetaUpdates(_ sender: UISwitch) { + // update it in database + UserDefaults.standard.isBetaUpdatesEnabled = sender.isOn + } + @IBAction func toggleIsBackgroundRefreshEnabled(_ sender: UISwitch) { UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn @@ -981,15 +987,15 @@ extension SettingsViewController self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: anisetteServersController), sender: nil) - case .advancedSettings: - // Create the URL that deep links to your app's custom settings. - if let url = URL(string: UIApplication.openSettingsURLString) { - // Ask the system to open that URL. - UIApplication.shared.open(url) - } else { - ELOG("UIApplication.openSettingsURLString invalid") - } - case .refreshAttempts, .responseCaching: break +// case .advancedSettings: +// // Create the URL that deep links to your app's custom settings. +// if let url = URL(string: UIApplication.openSettingsURLString) { +// // Ask the system to open that URL. +// UIApplication.shared.open(url) +// } else { +// ELOG("UIApplication.openSettingsURLString invalid") +// } + case .refreshAttempts, .responseCaching, .betaUpdates : break } diff --git a/AltStoreCore/Extensions/UserDefaults+AltStore.swift b/AltStoreCore/Extensions/UserDefaults+AltStore.swift index b3fe49fe..920c4a3a 100644 --- a/AltStoreCore/Extensions/UserDefaults+AltStore.swift +++ b/AltStoreCore/Extensions/UserDefaults+AltStore.swift @@ -32,6 +32,7 @@ public extension UserDefaults @NSManaged var isBackgroundRefreshEnabled: Bool @NSManaged var isIdleTimeoutDisableEnabled: Bool @NSManaged var isAppLimitDisabled: Bool + @NSManaged var isBetaUpdatesEnabled: Bool @NSManaged var isPairingReset: Bool @NSManaged var isDebugModeEnabled: Bool @NSManaged var presentedLaunchReminderNotification: Bool @@ -116,6 +117,7 @@ public extension UserDefaults let defaults = [ #keyPath(UserDefaults.isAppLimitDisabled): false, + #keyPath(UserDefaults.isBetaUpdatesEnabled): false, #keyPath(UserDefaults.isBackgroundRefreshEnabled): true, #keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true, #keyPath(UserDefaults.isPairingReset): true, diff --git a/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 17.xcdatamodel/contents b/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 17.xcdatamodel/contents index 4e8b5ab1..07e62ae5 100644 --- a/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 17.xcdatamodel/contents +++ b/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 17.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -62,6 +62,7 @@ + @@ -242,6 +243,7 @@ + @@ -295,4 +297,4 @@ - + \ No newline at end of file diff --git a/AltStoreCore/Model/AppVersion.swift b/AltStoreCore/Model/AppVersion.swift index 6c0b2c9a..dc94c71b 100644 --- a/AltStoreCore/Model/AppVersion.swift +++ b/AltStoreCore/Model/AppVersion.swift @@ -47,6 +47,7 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable @NSManaged public var appBundleID: String @NSManaged public var sourceID: String? @NSManaged public var isBeta: Bool + @NSManaged public var commitID: String? /* Relationships */ @NSManaged public private(set) var app: StoreApp? @@ -69,6 +70,7 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable case minOSVersion case maxOSVersion case isBeta + case commitID } public required init(from decoder: Decoder) throws @@ -91,9 +93,11 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable self.size = try container.decode(Int64.self, forKey: .size) self.sha256 = try container.decodeIfPresent(String.self, forKey: .sha256)?.lowercased() - self._minOSVersion = try container.decodeIfPresent(String.self, forKey: .minOSVersion) self._maxOSVersion = try container.decodeIfPresent(String.self, forKey: .maxOSVersion) + +// self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false +// self.commitID = try container.decodeIfPresent(String.self, forKey: .commitID) } catch { diff --git a/AltStoreCore/Model/InstalledApp.swift b/AltStoreCore/Model/InstalledApp.swift index 60f67ce6..a52b9927 100644 --- a/AltStoreCore/Model/InstalledApp.swift +++ b/AltStoreCore/Model/InstalledApp.swift @@ -77,18 +77,43 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol } @objc public var hasUpdate: Bool { - if self.storeApp == nil { return false } - if self.storeApp!.latestSupportedVersion == nil { return false } + guard let storeApp = self.storeApp, + let latestSupportedVersion = storeApp.latestSupportedVersion?.version else { + return false + } let currentVersion = SemanticVersion(self.version) - let latestVersion = SemanticVersion(self.storeApp!.latestSupportedVersion!.version) + let latestVersion = SemanticVersion(latestSupportedVersion) if currentVersion == nil || latestVersion == nil { - // One of the versions is not valid SemVer, fall back to comparing the version strings by character - return self.version < self.storeApp!.latestSupportedVersion!.version + return self.version < latestSupportedVersion } - return currentVersion! < latestVersion! + let isBeta = storeApp.isBeta + + // compare semantic version updates + // - for stable releases "beta" shouldn't be true + if !isBeta && (currentVersion! < latestVersion!) { + return true + } + + if UserDefaults.standard.isBetaUpdatesEnabled { + // NOTE: beta builds will always need commit ID suffix + // so it doesn't matter if semantic version was bumped, because commit ID won't be same + // and we will accept this update + + // storeApp.commitID is set in sources.json deployed at apps.json for the respective source + let commitID = storeApp.commitID ?? "" + if(isBeta && !commitID.isEmpty){ + let SHORT_COMMIT_LEN = 7 + let isCommitIDValid = (commitID.count == SHORT_COMMIT_LEN) + let installedAppCommitID = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? "" +// let isBetaUpdateAvailable = (installedAppCommitID.count == commitID.count) && + let isBetaUpdateAvailable = (installedAppCommitID != commitID) + return isCommitIDValid && isBetaUpdateAvailable + } + } + return false } public var appIDCount: Int { diff --git a/AltStoreCore/Model/StoreApp.swift b/AltStoreCore/Model/StoreApp.swift index 2f29446c..b24810e1 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -151,6 +151,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable @NSManaged public private(set) var tintColor: UIColor? @NSManaged public private(set) var isBeta: Bool + @NSManaged public private(set) var commitID: String? // Required for Marketplace apps. @NSManaged public private(set) var marketplaceID: String? @@ -289,6 +290,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable case permissions = "appPermissions" case size case isBeta = "beta" + case commitID case versions case patreon case category @@ -311,6 +313,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable do { let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) self.bundleIdentifier = try container.decode(String.self, forKey: .bundleIdentifier) self.developerName = try container.decode(String.self, forKey: .developerName) @@ -319,6 +322,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable self.subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle) self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false + self.commitID = try container.decodeIfPresent(String.self, forKey: .commitID) var downloadURL = try container.decodeIfPresent(URL.self, forKey: .downloadURL) let platformURLs = try container.decodeIfPresent(PlatformURLs.self.self, forKey: .platformURLs)