summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-10-10 07:11:22 -0700
committerMitchell Hashimoto <m@mitchellh.com>2025-10-10 08:27:30 -0700
commit6993947a3a8a8c92d849fa1fa23a9e9fa4016ea8 (patch)
treee5a9023ebec40486a5907d32588f8be156bfd8fb
parentba8eae027e7f5496df37bdd6acf30bf8f8b72854 (diff)
macOS: Make a lot of things more robust
-rw-r--r--macos/Sources/App/macOS/AppDelegate.swift4
-rw-r--r--macos/Sources/Features/Update/UpdateBadge.swift4
-rw-r--r--macos/Sources/Features/Update/UpdateController.swift17
-rw-r--r--macos/Sources/Features/Update/UpdateDriver.swift5
-rw-r--r--macos/Sources/Features/Update/UpdatePill.swift13
-rw-r--r--macos/Sources/Features/Update/UpdatePopoverView.swift6
6 files changed, 34 insertions, 15 deletions
diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift
index 216373e7e..cf717993a 100644
--- a/macos/Sources/App/macOS/AppDelegate.swift
+++ b/macos/Sources/App/macOS/AppDelegate.swift
@@ -998,8 +998,8 @@ class AppDelegate: NSObject,
}
@IBAction func checkForUpdates(_ sender: Any?) {
- updateController.checkForUpdates()
- //UpdateSimulator.permissionRequest.simulate(with: updateViewModel)
+ //updateController.checkForUpdates()
+ UpdateSimulator.happyPath.simulate(with: updateViewModel)
}
diff --git a/macos/Sources/Features/Update/UpdateBadge.swift b/macos/Sources/Features/Update/UpdateBadge.swift
index fd1eb3498..afd0849be 100644
--- a/macos/Sources/Features/Update/UpdateBadge.swift
+++ b/macos/Sources/Features/Update/UpdateBadge.swift
@@ -17,14 +17,14 @@ struct UpdateBadge: View {
switch model.state {
case .downloading(let download):
if let expectedLength = download.expectedLength, expectedLength > 0 {
- let progress = Double(download.progress) / Double(expectedLength)
+ let progress = min(1, max(0, Double(download.progress) / Double(expectedLength)))
ProgressRingView(progress: progress)
} else {
Image(systemName: "arrow.down.circle")
}
case .extracting(let extracting):
- ProgressRingView(progress: extracting.progress)
+ ProgressRingView(progress: min(1, max(0, extracting.progress)))
case .checking, .installing:
if let iconName = model.iconName {
diff --git a/macos/Sources/Features/Update/UpdateController.swift b/macos/Sources/Features/Update/UpdateController.swift
index 8dc24698b..446b82ebc 100644
--- a/macos/Sources/Features/Update/UpdateController.swift
+++ b/macos/Sources/Features/Update/UpdateController.swift
@@ -32,9 +32,22 @@ class UpdateController {
/// Start the updater.
///
/// This must be called before the updater can check for updates. If starting fails,
- /// an error alert will be shown after a short delay.
+ /// the error will be shown to the user.
func startUpdater() {
- try? updater.start()
+ do {
+ try updater.start()
+ } catch {
+ userDriver.viewModel.state = .error(.init(
+ error: error,
+ retry: { [weak self] in
+ self?.userDriver.viewModel.state = .idle
+ self?.startUpdater()
+ },
+ dismiss: { [weak self] in
+ self?.userDriver.viewModel.state = .idle
+ }
+ ))
+ }
}
/// Check for updates.
diff --git a/macos/Sources/Features/Update/UpdateDriver.swift b/macos/Sources/Features/Update/UpdateDriver.swift
index cd1d051e2..9196d9ad9 100644
--- a/macos/Sources/Features/Update/UpdateDriver.swift
+++ b/macos/Sources/Features/Update/UpdateDriver.swift
@@ -86,9 +86,10 @@ class UpdateDriver: NSObject, SPUUserDriver {
acknowledgement: @escaping () -> Void) {
viewModel.state = .error(.init(
error: error,
- retry: { [weak viewModel] in
+ retry: { [weak self, weak viewModel] in
viewModel?.state = .idle
- DispatchQueue.main.async {
+ DispatchQueue.main.async { [weak self] in
+ guard let self else { return }
guard let delegate = NSApp.delegate as? AppDelegate else { return }
delegate.checkForUpdates(self)
}
diff --git a/macos/Sources/Features/Update/UpdatePill.swift b/macos/Sources/Features/Update/UpdatePill.swift
index b975e81c9..3b48ac218 100644
--- a/macos/Sources/Features/Update/UpdatePill.swift
+++ b/macos/Sources/Features/Update/UpdatePill.swift
@@ -8,6 +8,9 @@ struct UpdatePill: View {
/// Whether the update popover is currently visible
@State private var showPopover = false
+ /// Task for auto-dismissing the "No Updates" state
+ @State private var resetTask: Task<Void, Never>?
+
/// The font used for the pill text
private let textFont = NSFont.systemFont(ofSize: 11, weight: .medium)
@@ -19,13 +22,15 @@ struct UpdatePill: View {
}
.transition(.opacity.combined(with: .scale(scale: 0.95)))
.onChange(of: model.state) { newState in
+ resetTask?.cancel()
if case .notFound = newState {
- Task {
+ resetTask = Task { [weak model] in
try? await Task.sleep(for: .seconds(5))
- if case .notFound = model.state {
- model.state = .idle
- }
+ guard !Task.isCancelled, case .notFound? = model?.state else { return }
+ model?.state = .idle
}
+ } else {
+ resetTask = nil
}
}
}
diff --git a/macos/Sources/Features/Update/UpdatePopoverView.swift b/macos/Sources/Features/Update/UpdatePopoverView.swift
index a73116ca0..7634d27de 100644
--- a/macos/Sources/Features/Update/UpdatePopoverView.swift
+++ b/macos/Sources/Features/Update/UpdatePopoverView.swift
@@ -228,7 +228,7 @@ fileprivate struct DownloadingView: View {
.font(.system(size: 13, weight: .semibold))
if let expectedLength = download.expectedLength, expectedLength > 0 {
- let progress = Double(download.progress) / Double(expectedLength)
+ let progress = min(1, max(0, Double(download.progress) / Double(expectedLength)))
VStack(alignment: .leading, spacing: 6) {
ProgressView(value: progress)
Text(String(format: "%.0f%%", progress * 100))
@@ -264,8 +264,8 @@ fileprivate struct ExtractingView: View {
.font(.system(size: 13, weight: .semibold))
VStack(alignment: .leading, spacing: 6) {
- ProgressView(value: extracting.progress, total: 1.0)
- Text(String(format: "%.0f%%", extracting.progress * 100))
+ ProgressView(value: min(1, max(0, extracting.progress)), total: 1.0)
+ Text(String(format: "%.0f%%", min(1, max(0, extracting.progress)) * 100))
.font(.system(size: 11))
.foregroundColor(.secondary)
}