diff --git a/macos/Sources/Features/Update/UpdatePill.swift b/macos/Sources/Features/Update/UpdatePill.swift index dda9ad607..1dc29e250 100644 --- a/macos/Sources/Features/Update/UpdatePill.swift +++ b/macos/Sources/Features/Update/UpdatePill.swift @@ -15,13 +15,29 @@ struct UpdatePill: View { UpdatePopoverView(model: model) } .transition(.opacity.combined(with: .scale(scale: 0.95))) + .onChange(of: model.state) { newState in + if case .notFound = newState { + Task { + try? await Task.sleep(for: .seconds(5)) + if case .notFound = model.state { + model.state = .idle + } + } + } + } } } /// The pill-shaped button view that displays the update badge and text @ViewBuilder private var pillButton: some View { - Button(action: { showPopover.toggle() }) { + Button(action: { + if case .notFound = model.state { + model.state = .idle + } else { + showPopover.toggle() + } + }) { HStack(spacing: 6) { UpdateBadge(model: model) .frame(width: 14, height: 14) diff --git a/macos/Sources/Features/Update/UpdateViewModel.swift b/macos/Sources/Features/Update/UpdateViewModel.swift index 57e438bcd..05f7eef9a 100644 --- a/macos/Sources/Features/Update/UpdateViewModel.swift +++ b/macos/Sources/Features/Update/UpdateViewModel.swift @@ -87,6 +87,8 @@ class UpdateViewModel: ObservableObject { return .accentColor case .readyToInstall: return Color(nsColor: NSColor.systemGreen.blended(withFraction: 0.3, of: .black) ?? .systemGreen) + case .notFound: + return Color(nsColor: NSColor.systemBlue.blended(withFraction: 0.5, of: .black) ?? .systemBlue) case .error: return .orange.opacity(0.2) default: @@ -99,6 +101,8 @@ class UpdateViewModel: ObservableObject { switch state { case .updateAvailable, .readyToInstall: return .white + case .notFound: + return .white case .error: return .orange default: @@ -107,7 +111,7 @@ class UpdateViewModel: ObservableObject { } } -enum UpdateState { +enum UpdateState: Equatable { case idle case permissionRequest(PermissionRequest) case checking(Checking) @@ -124,6 +128,33 @@ enum UpdateState { return false } + static func == (lhs: UpdateState, rhs: UpdateState) -> Bool { + switch (lhs, rhs) { + case (.idle, .idle): + return true + case (.permissionRequest, .permissionRequest): + return true + case (.checking, .checking): + return true + case (.updateAvailable(let lUpdate), .updateAvailable(let rUpdate)): + return lUpdate.appcastItem.displayVersionString == rUpdate.appcastItem.displayVersionString + case (.notFound, .notFound): + return true + case (.error(let lErr), .error(let rErr)): + return lErr.error.localizedDescription == rErr.error.localizedDescription + case (.downloading(let lDown), .downloading(let rDown)): + return lDown.progress == rDown.progress && lDown.expectedLength == rDown.expectedLength + case (.extracting(let lExt), .extracting(let rExt)): + return lExt.progress == rExt.progress + case (.readyToInstall, .readyToInstall): + return true + case (.installing, .installing): + return true + default: + return false + } + } + struct PermissionRequest { let request: SPUUpdatePermissionRequest let reply: @Sendable (SUUpdatePermissionResponse) -> Void