Show update-available banner automatically on launch (#1651)
* Show update-available banner automatically on launch Probe for updates immediately on launch via Sparkle's checkForUpdateInformation() so the sidebar surfaces a passive update indicator without waiting for the 24h scheduler. When Sparkle detects an available update in the background, the pill now shows "Update Available: X.Y.Z" with accent styling while the updater is idle. Clicking it triggers the full interactive update flow. Also fixes thread safety in delegate callbacks by dispatching @Published mutations to the main queue. Closes #1643 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add periodic background update probe every 15 minutes The launch-only probe wouldn't catch updates published while the app is already running. Add a repeating 15-minute timer that calls checkForUpdateInformation() so the sidebar banner appears within a reasonable window after a new version is published. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Change background update probe interval to 30 minutes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Change update check interval to 1 hour and migrate existing users Reduce Sparkle's scheduled check interval from 24h to 1h so update banners appear sooner. Migrate users stuck on the old 24h default by bumping the migration key to v2. Align background probe interval with the Sparkle check interval instead of hardcoding 30 minutes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
63e65a7f5c
commit
e203c51c7a
7 changed files with 147 additions and 32 deletions
|
|
@ -15,6 +15,14 @@ class UpdateViewModel: ObservableObject {
|
|||
overrideState ?? state
|
||||
}
|
||||
|
||||
var showsDetectedBackgroundUpdate: Bool {
|
||||
effectiveState.isIdle && detectedUpdateVersion != nil
|
||||
}
|
||||
|
||||
var showsPill: Bool {
|
||||
!effectiveState.isIdle || showsDetectedBackgroundUpdate
|
||||
}
|
||||
|
||||
func recordDetectedUpdate(_ item: SUAppcastItem) {
|
||||
detectedUpdateVersion = Self.normalizedDetectedUpdateVersion(from: item.displayVersionString)
|
||||
}
|
||||
|
|
@ -27,6 +35,9 @@ class UpdateViewModel: ObservableObject {
|
|||
#if DEBUG
|
||||
if let debugOverrideText { return debugOverrideText }
|
||||
#endif
|
||||
if let detectedText = detectedUpdateText {
|
||||
return detectedText
|
||||
}
|
||||
switch effectiveState {
|
||||
case .idle:
|
||||
return ""
|
||||
|
|
@ -60,6 +71,9 @@ class UpdateViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
var maxWidthText: String {
|
||||
if let detectedText = detectedUpdateText {
|
||||
return detectedText
|
||||
}
|
||||
switch effectiveState {
|
||||
case .downloading:
|
||||
return "Downloading: 100%"
|
||||
|
|
@ -71,6 +85,9 @@ class UpdateViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
var iconName: String? {
|
||||
if showsDetectedBackgroundUpdate {
|
||||
return "shippingbox.fill"
|
||||
}
|
||||
switch effectiveState {
|
||||
case .idle:
|
||||
return nil
|
||||
|
|
@ -135,6 +152,9 @@ class UpdateViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
var iconColor: Color {
|
||||
if showsDetectedBackgroundUpdate {
|
||||
return cmuxAccentColor()
|
||||
}
|
||||
switch effectiveState {
|
||||
case .idle:
|
||||
return .secondary
|
||||
|
|
@ -154,6 +174,9 @@ class UpdateViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
var backgroundColor: Color {
|
||||
if showsDetectedBackgroundUpdate {
|
||||
return cmuxAccentColor()
|
||||
}
|
||||
switch effectiveState {
|
||||
case .permissionRequest:
|
||||
return Color(nsColor: NSColor.systemBlue.blended(withFraction: 0.3, of: .black) ?? .systemBlue)
|
||||
|
|
@ -169,6 +192,9 @@ class UpdateViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
var foregroundColor: Color {
|
||||
if showsDetectedBackgroundUpdate {
|
||||
return .white
|
||||
}
|
||||
switch effectiveState {
|
||||
case .permissionRequest:
|
||||
return .white
|
||||
|
|
@ -348,6 +374,11 @@ class UpdateViewModel: ObservableObject {
|
|||
let trimmed = version.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
|
||||
private var detectedUpdateText: String? {
|
||||
guard showsDetectedBackgroundUpdate, let version = detectedUpdateVersion else { return nil }
|
||||
return String(localized: "update.available.withVersion", defaultValue: "Update Available: \(version)")
|
||||
}
|
||||
}
|
||||
|
||||
enum UpdateState: Equatable {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue