import Foundation #if os(macOS) import AppKit #endif @MainActor final class UpdateViewModel: ObservableObject { struct StatusAlert: Identifiable { let id = UUID() let title: String let message: String } @Published var availableRelease: ReleaseInfo? @Published var latestFetchedRelease: ReleaseInfo? @Published var statusAlert: StatusAlert? @Published var isChecking = false private let service: UpdateService private let userDefaults: UserDefaults private let autoCheckKey = "autoCheckUpdates" private let includePrereleaseKey = "includePrereleaseUpdates" init(service: UpdateService = UpdateService(), userDefaults: UserDefaults = .standard) { self.service = service self.userDefaults = userDefaults registerDefaultsIfNeeded() } func startAutomaticCheckIfNeeded() { if userDefaults.bool(forKey: autoCheckKey) { checkForUpdates(userInitiated: false) } } func checkForUpdates(userInitiated: Bool) { guard !isChecking else { return } isChecking = true Task { do { let release = try await service.fetchLatestRelease(includePrerelease: userDefaults.bool(forKey: includePrereleaseKey)) handle(release: release, userInitiated: userInitiated) } catch { if userInitiated { statusAlert = StatusAlert( title: "Update Check Failed", message: error.localizedDescription ) } } self.isChecking = false } } func openReleaseNotes() { guard let releaseURL = availableRelease?.releaseURL ?? latestFetchedRelease?.releaseURL else { return } #if os(macOS) NSWorkspace.shared.open(releaseURL) #endif } func downloadLatest() { let release = availableRelease ?? latestFetchedRelease availableRelease = nil guard let downloadURL = release?.downloadURL else { return } #if os(macOS) NSWorkspace.shared.open(downloadURL) #endif } private func handle(release: ReleaseInfo, userInitiated: Bool) { let currentVersion = VersionNumber( Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0" ) latestFetchedRelease = release if release.version > currentVersion { availableRelease = release } else if userInitiated { statusAlert = StatusAlert( title: "You're Up To Date", message: "iKeyMon \(currentVersion.rawValue) is the latest version." ) } } private func registerDefaultsIfNeeded() { userDefaults.register(defaults: [ autoCheckKey: true, includePrereleaseKey: true ]) } }