From 50da591a6a603b902e6a8e8b0335d5eea98bbb99 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 16 Jun 2024 18:25:44 +0200 Subject: [PATCH] Allow changelog URL callback to be set --- Package.swift | 6 +- Sources/AppUpdater/API/UpdateCheck.swift | 74 +++++++++++++---------- Sources/AppUpdater/Support/CaskFile.swift | 2 +- 3 files changed, 49 insertions(+), 33 deletions(-) diff --git a/Package.swift b/Package.swift index 7e7daf1..f2832d5 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,11 @@ let package = Package( products: [ .library(name: "NVAppUpdater", targets: ["NVAppUpdater"]), ], + dependencies: [ + .package(name: "NVAlert", path: "/Users/nicoverbruggen/Code/SwiftPM/NVAlert") + // .package(url: "https://github.com/nicoverbruggen/NVAlert", branch: "main") + ], targets: [ - .target(name: "NVAppUpdater"), + .target(name: "NVAppUpdater", dependencies: ["NVAlert"]), ] ) diff --git a/Sources/AppUpdater/API/UpdateCheck.swift b/Sources/AppUpdater/API/UpdateCheck.swift index 53e387c..561bf6f 100644 --- a/Sources/AppUpdater/API/UpdateCheck.swift +++ b/Sources/AppUpdater/API/UpdateCheck.swift @@ -5,16 +5,17 @@ import Foundation import Cocoa +import NVAlert open class UpdateCheck { let caskUrl: URL - let promptOnFailure: Bool let selfUpdaterName: String let selfUpdaterPath: String - var caskFile: CaskFile! - var newerVersion: AppVersion! + private var releaseNotesUrlCallback: ((CaskFile) -> URL?)? = nil + private var caskFile: CaskFile! + private var newerVersion: AppVersion! /** * Create a new update check instance. Once created, you should call `perform` on this instance. @@ -28,30 +29,33 @@ open class UpdateCheck * * - Parameter caskUrl: The URL where the Cask file is expected to be located. Redirects will * be followed when retrieving and validating the Cask file. - * - * - 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 init( selfUpdaterName: String, selfUpdaterPath: String, - caskUrl: URL, - promptOnFailure: Bool + caskUrl: URL ) { self.selfUpdaterName = selfUpdaterName self.selfUpdaterPath = selfUpdaterPath self.caskUrl = caskUrl - self.promptOnFailure = promptOnFailure + } + + public func resolvingReleaseNotes(with callback: @escaping (CaskFile) -> URL?) -> Self { + self.releaseNotesUrlCallback = callback + return self } /** - 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() async { - guard let caskFile = await CaskFile.from(url: caskUrl) else { + public func perform(promptOnFailure: Bool = true) async { + guard let caskFile = CaskFile.from(url: caskUrl) else { Log.text("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.") - return await presentCouldNotRetrieveUpdate() + return await presentCouldNotRetrieveUpdate(promptOnFailure) } self.caskFile = caskFile @@ -60,7 +64,7 @@ open class UpdateCheck guard let onlineVersion = AppVersion.from(caskFile.version) else { Log.text("The version string from the CaskFile could not be read.") - return await presentCouldNotRetrieveUpdate() + return await presentCouldNotRetrieveUpdate(promptOnFailure) } self.newerVersion = onlineVersion @@ -71,13 +75,13 @@ open class UpdateCheck if onlineVersion > currentVersion { await presentNewerVersionAvailable() } else if promptOnFailure { - await presentVersionIsUpToDate() + await presentVersionIsUpToDate(promptOnFailure) } } // MARK: - Alerts - private func presentCouldNotRetrieveUpdate() async { + private func presentCouldNotRetrieveUpdate(_ promptOnFailure: Bool) async { Log.text("Could not retrieve update manifest!") if promptOnFailure { @@ -88,7 +92,7 @@ open class UpdateCheck } } - private func presentVersionIsUpToDate() async { + private func presentVersionIsUpToDate(_ promptOnFailure: Bool) async { Log.text("Version is up-to-date!") if promptOnFailure { @@ -104,22 +108,30 @@ open class UpdateCheck let current = AppVersion.fromCurrentVersion() - let outcome = await Alert.choose( + let alert = await NVAlert().withInformation( title: "An updated version of \(Executable.name) is available.", - description: """ - Version \(newerVersion.version) is available for download. - (This is currently version \(current.version).) + subtitle: "Version \(newerVersion.version) is available for download.", + description: "Do you want to download and install this updated version?" + ) + .withPrimary( + text: "Install", + action: { vc in + vc.close(with: .OK) + self.launchSelfUpdater() + } + ) + .withTertiary(text: "Dismiss", action: { vc in + vc.close(with: .OK) + }) - Do you want to download and install this updated version? - """, - options: [ - "Update Now", - "Cancel" - ]) - - if outcome == .alertFirstButtonReturn { - launchSelfUpdater() + if let callback = self.releaseNotesUrlCallback, + let url = callback(self.caskFile) { + await alert.withSecondary(text: "View Release Notes") { _ in + NSWorkspace.shared.open(url) + } } + + await alert.show() } // MARK: - Functional diff --git a/Sources/AppUpdater/Support/CaskFile.swift b/Sources/AppUpdater/Support/CaskFile.swift index dde7295..043b161 100644 --- a/Sources/AppUpdater/Support/CaskFile.swift +++ b/Sources/AppUpdater/Support/CaskFile.swift @@ -5,7 +5,7 @@ import Foundation -struct CaskFile { +public struct CaskFile { var properties: [String: String] var name: String { return self.properties["name"]! }