Use NVAlert 2.0 with urgency

This commit is contained in:
2025-11-27 13:31:21 +01:00
parent bfe441fb2f
commit aa9658754d
4 changed files with 37 additions and 31 deletions

View File

@@ -1,13 +1,13 @@
{ {
"originHash" : "0b9d136f905253e9368420218eda771d18cfa528b15553cb08a34c9e5ecdfea7", "originHash" : "699c69d1c5c91fc14af71a7c0e846526c570ff854487f4866cea7b5caa49b7fe",
"pins" : [ "pins" : [
{ {
"identity" : "nvalert", "identity" : "nvalert",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/nicoverbruggen/NVAlert", "location" : "https://github.com/nicoverbruggen/NVAlert",
"state" : { "state" : {
"revision" : "2d649465067e3fc053bc64beed0e2ffea7e1cbe2", "revision" : "1bbaad37697e4ad4c047eec2a8fc9834c2b5d98a",
"version" : "1.0.0" "version" : "2.0.0"
} }
} }
], ],

View File

@@ -10,7 +10,7 @@ let package = Package(
.library(name: "NVAppUpdater", targets: ["NVAppUpdater"]), .library(name: "NVAppUpdater", targets: ["NVAppUpdater"]),
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/nicoverbruggen/NVAlert", from: "1.0.0") .package(url: "https://github.com/nicoverbruggen/NVAlert", from: "2.0.0")
], ],
targets: [ targets: [
.target(name: "NVAppUpdater", dependencies: ["NVAlert"]), .target(name: "NVAppUpdater", dependencies: ["NVAlert"]),

View File

@@ -68,7 +68,8 @@ await UpdateCheck(
selfUpdaterName: "MyApp Self-Updater.app", selfUpdaterName: "MyApp Self-Updater.app",
selfUpdaterPath: "~/.config/com.example.my-app/updater", selfUpdaterPath: "~/.config/com.example.my-app/updater",
caskUrl: URL(string: "https://my-app.test/latest/build.rb")!, caskUrl: URL(string: "https://my-app.test/latest/build.rb")!,
).perform(promptOnFailure: true) isInteractive: true,
).perform()
``` ```
You can also specify what callback needs to be used to determine the correct URL for the release notes. You may need to get some information from the CaskFile, which you are free to source. For example: You can also specify what callback needs to be used to determine the correct URL for the release notes. You may need to get some information from the CaskFile, which you are free to source. For example:
@@ -80,11 +81,12 @@ await UpdateCheck(
selfUpdaterName: "MyApp Self-Updater.app", selfUpdaterName: "MyApp Self-Updater.app",
selfUpdaterPath: "~/.config/com.example.my-app/updater", selfUpdaterPath: "~/.config/com.example.my-app/updater",
caskUrl: URL(string: "https://my-app.test/latest/build.rb")!, caskUrl: URL(string: "https://my-app.test/latest/build.rb")!,
isInteractive: true,
) )
.resolvingReleaseNotes(with: { caskFile in .resolvingReleaseNotes(with: { caskFile in
return URL(string: "https://my-app.com/release-notes/\(caskFile.version)")! return URL(string: "https://my-app.com/release-notes/\(caskFile.version)")!
}) })
.perform(promptOnFailure: true) .perform()
``` ```
## Self-Updater ## Self-Updater

View File

@@ -42,6 +42,7 @@ open class UpdateCheck
let caskUrl: URL let caskUrl: URL
let selfUpdaterName: String let selfUpdaterName: String
let selfUpdaterPath: String let selfUpdaterPath: String
let isInteractive: Bool
private var releaseNotesUrlCallback: ((NVCaskFile) -> URL?)? = nil private var releaseNotesUrlCallback: ((NVCaskFile) -> URL?)? = nil
private var caskFile: NVCaskFile! private var caskFile: NVCaskFile!
@@ -59,15 +60,21 @@ open class UpdateCheck
* *
* - Parameter caskUrl: The URL where the Cask file is expected to be located. Redirects will * - Parameter caskUrl: The URL where the Cask file is expected to be located. Redirects will
* be followed when retrieving and validating the Cask file. * be followed when retrieving and validating the Cask file.
*
* - Parameter isInteractive: Whether user interaction is required when failing to check
* or no new update is found. A user usually expects a prompt if they manually searched
* for updates.
*/ */
public init( public init(
selfUpdaterName: String, selfUpdaterName: String,
selfUpdaterPath: String, selfUpdaterPath: String,
caskUrl: URL caskUrl: URL,
isInteractive: Bool
) { ) {
self.selfUpdaterName = selfUpdaterName self.selfUpdaterName = selfUpdaterName
self.selfUpdaterPath = selfUpdaterPath self.selfUpdaterPath = selfUpdaterPath
self.caskUrl = caskUrl self.caskUrl = caskUrl
self.isInteractive = isInteractive
} }
/** /**
@@ -82,15 +89,11 @@ open class UpdateCheck
/** /**
* Perform the check for a new version. * Perform the check for a new version.
*
* - Parameter promptOnFailure: Whether user interaction is required when failing to check
* or no new update is found. A user usually expects a prompt if they manually searched
* for updates.
*/ */
public func perform(promptOnFailure: Bool = true) async { public func perform() async {
guard let caskFile = NVCaskFile.from(url: caskUrl) else { guard let caskFile = NVCaskFile.from(url: caskUrl) else {
Log.text("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.") Log.text("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.")
return await presentCouldNotRetrieveUpdate(promptOnFailure) return await presentCouldNotRetrieveUpdate()
} }
self.caskFile = caskFile self.caskFile = caskFile
@@ -99,7 +102,7 @@ open class UpdateCheck
guard let onlineVersion = AppVersion.from(caskFile.version) else { guard let onlineVersion = AppVersion.from(caskFile.version) else {
Log.text("The version string from the CaskFile could not be read.") Log.text("The version string from the CaskFile could not be read.")
return await presentCouldNotRetrieveUpdate(promptOnFailure) return await presentCouldNotRetrieveUpdate()
} }
self.newerVersion = onlineVersion self.newerVersion = onlineVersion
@@ -108,29 +111,19 @@ open class UpdateCheck
Log.text("The current version is v\(currentVersion.computerReadable).") Log.text("The current version is v\(currentVersion.computerReadable).")
if onlineVersion > currentVersion { if onlineVersion > currentVersion {
// A newer version is available
await presentNewerVersionAvailable() await presentNewerVersionAvailable()
} else if promptOnFailure { } else {
await presentVersionIsUpToDate(promptOnFailure) await presentVersionUpToDate()
} }
} }
// MARK: - Alerts // MARK: - Alerts
private func presentCouldNotRetrieveUpdate(_ promptOnFailure: Bool) async { private func presentVersionUpToDate() async {
Log.text("Could not retrieve update manifest!") Log.text("Application is up-to-date!")
if promptOnFailure { if isInteractive {
await Alert.confirm(
title: translations.couldNotRetrieveUpdateTitle,
description: translations.couldNotRetrieveUpdateDescription
)
}
}
private func presentVersionIsUpToDate(_ promptOnFailure: Bool) async {
Log.text("Version is up-to-date!")
if promptOnFailure {
await Alert.confirm( await Alert.confirm(
title: translations.appIsUpToDateTitle title: translations.appIsUpToDateTitle
.replacingOccurrences(of: "%@", with: Executable.name), .replacingOccurrences(of: "%@", with: Executable.name),
@@ -139,6 +132,17 @@ open class UpdateCheck
} }
} }
private func presentCouldNotRetrieveUpdate() async {
Log.text("Could not retrieve update manifest!")
if isInteractive {
await Alert.confirm(
title: translations.couldNotRetrieveUpdateTitle,
description: translations.couldNotRetrieveUpdateDescription
)
}
}
private func presentNewerVersionAvailable() async { private func presentNewerVersionAvailable() async {
Log.text("A newer version is available!") Log.text("A newer version is available!")
@@ -167,7 +171,7 @@ open class UpdateCheck
} }
} }
await alert.show() await alert.show(urgency: isInteractive ? .bringToFront : .urgentRequestAttention)
} }
// MARK: - Functional // MARK: - Functional