Downloads latest _available_ version when updating from AppViewController

Asks user to fall back to latest supported verson if version is not compatible with device’s iOS version.
This commit is contained in:
Riley Testut
2023-12-01 16:03:06 -06:00
parent 2c1ffedfe3
commit 92f3be07f6
3 changed files with 47 additions and 30 deletions

View File

@@ -360,13 +360,21 @@ private extension AppViewController
{ {
func update() func update()
{ {
var buttonAction: AppBannerView.AppAction?
if let installedApp = self.app.installedApp, let latestVersion = self.app.latestAvailableVersion, !installedApp.matches(latestVersion)
{
// Explicitly set button action to .update if there is an update available, even if it's not supported.
buttonAction = .update
}
for button in [self.bannerView.button!, self.navigationBarDownloadButton!] for button in [self.bannerView.button!, self.navigationBarDownloadButton!]
{ {
button.tintColor = self.app.tintColor button.tintColor = self.app.tintColor
button.isIndicatingActivity = false button.isIndicatingActivity = false
} }
self.bannerView.configure(for: self.app) self.bannerView.configure(for: self.app, action: buttonAction)
let title = self.bannerView.button.title(for: .normal) let title = self.bannerView.button.title(for: .normal)
self.navigationBarDownloadButton.setTitle(title, for: .normal) self.navigationBarDownloadButton.setTitle(title, for: .normal)
@@ -498,9 +506,9 @@ extension AppViewController
{ {
if let installedApp = self.app.installedApp if let installedApp = self.app.installedApp
{ {
if let latestVersion = self.app.latestSupportedVersion, !installedApp.matches(latestVersion) if let latestVersion = self.app.latestAvailableVersion, !installedApp.matches(latestVersion)
{ {
self.updateApp(installedApp) self.updateApp(installedApp, to: latestVersion)
} }
else else
{ {
@@ -551,7 +559,7 @@ extension AppViewController
UIApplication.shared.open(installedApp.openAppURL) UIApplication.shared.open(installedApp.openAppURL)
} }
func updateApp(_ installedApp: InstalledApp) func updateApp(_ installedApp: InstalledApp, to version: AppVersion)
{ {
let previousProgress = AppManager.shared.installationProgress(for: installedApp) let previousProgress = AppManager.shared.installationProgress(for: installedApp)
guard previousProgress == nil else { guard previousProgress == nil else {
@@ -560,7 +568,7 @@ extension AppViewController
return return
} }
_ = AppManager.shared.update(installedApp, presentingViewController: self) { (result) in AppManager.shared.update(installedApp, to: version, presentingViewController: self) { (result) in
DispatchQueue.main.async { DispatchQueue.main.async {
switch result switch result
{ {

View File

@@ -557,9 +557,9 @@ extension AppManager
} }
@discardableResult @discardableResult
func update(_ installedApp: InstalledApp, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress func update(_ installedApp: InstalledApp, to version: AppVersion? = nil, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
{ {
guard let appVersion = installedApp.storeApp?.latestSupportedVersion else { guard let appVersion = version ?? installedApp.storeApp?.latestSupportedVersion else {
completionHandler(.failure(OperationError.appNotFound(name: installedApp.name))) completionHandler(.failure(OperationError.appNotFound(name: installedApp.name)))
return Progress.discreteProgress(totalUnitCount: 1) return Progress.discreteProgress(totalUnitCount: 1)
} }

View File

@@ -17,7 +17,9 @@ import Roxas
@objc(DownloadAppOperation) @objc(DownloadAppOperation)
class DownloadAppOperation: ResultOperation<ALTApplication> class DownloadAppOperation: ResultOperation<ALTApplication>
{ {
let app: AppProtocol @Managed
private(set) var app: AppProtocol
let context: InstallAppOperationContext let context: InstallAppOperationContext
private let appName: String private let appName: String
@@ -59,22 +61,36 @@ class DownloadAppOperation: ResultOperation<ALTApplication>
// Set _after_ checking self.context.error to prevent overwriting localized failure for previous errors. // Set _after_ checking self.context.error to prevent overwriting localized failure for previous errors.
self.localizedFailure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), self.appName) self.localizedFailure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), self.appName)
guard let storeApp = self.app as? StoreApp else { self.$app.perform { app in
// Only StoreApp allows falling back to previous versions.
// AppVersion can only install itself, and ALTApplication doesn't have previous versions.
return self.download(self.app)
}
// Verify storeApp
storeApp.managedObjectContext?.perform {
do do
{ {
let latestVersion = try self.verify(storeApp) var appVersion: AppVersion?
self.download(latestVersion)
if let version = app as? AppVersion
{
appVersion = version
}
else if let storeApp = app as? StoreApp
{
guard let latestVersion = storeApp.latestAvailableVersion else {
let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName)
throw OperationError.unknown(failureReason: failureReason)
}
// Attempt to download latest _available_ version, and fall back to older versions if necessary.
appVersion = latestVersion
}
if let appVersion
{
try self.verify(appVersion)
}
self.download(appVersion ?? app)
} }
catch let error as VerificationError where error.code == .iOSVersionNotSupported catch let error as VerificationError where error.code == .iOSVersionNotSupported
{ {
guard let presentingViewController = self.context.presentingViewController, let latestSupportedVersion = storeApp.latestSupportedVersion guard let presentingViewController = self.context.presentingViewController, let storeApp = app.storeApp, let latestSupportedVersion = storeApp.latestSupportedVersion
else { return self.finish(.failure(error)) } else { return self.finish(.failure(error)) }
if let installedApp = storeApp.installedApp if let installedApp = storeApp.installedApp
@@ -121,23 +137,16 @@ class DownloadAppOperation: ResultOperation<ALTApplication>
private extension DownloadAppOperation private extension DownloadAppOperation
{ {
func verify(_ storeApp: StoreApp) throws -> AppVersion func verify(_ version: AppVersion) throws
{ {
guard let version = storeApp.latestAvailableVersion else {
let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName)
throw OperationError.unknown(failureReason: failureReason)
}
if let minOSVersion = version.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) if let minOSVersion = version.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion)
{ {
throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: minOSVersion) throw VerificationError.iOSVersionNotSupported(app: version, requiredOSVersion: minOSVersion)
} }
else if let maxOSVersion = version.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion else if let maxOSVersion = version.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion
{ {
throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: maxOSVersion) throw VerificationError.iOSVersionNotSupported(app: version, requiredOSVersion: maxOSVersion)
} }
return version
} }
func download(@Managed _ app: AppProtocol) func download(@Managed _ app: AppProtocol)