summaryrefslogtreecommitdiff
path: root/macos/Sources/Features/Update/UpdatePopoverView.swift
diff options
context:
space:
mode:
Diffstat (limited to 'macos/Sources/Features/Update/UpdatePopoverView.swift')
-rw-r--r--macos/Sources/Features/Update/UpdatePopoverView.swift216
1 files changed, 105 insertions, 111 deletions
diff --git a/macos/Sources/Features/Update/UpdatePopoverView.swift b/macos/Sources/Features/Update/UpdatePopoverView.swift
index af870b4de..39c4ac5c9 100644
--- a/macos/Sources/Features/Update/UpdatePopoverView.swift
+++ b/macos/Sources/Features/Update/UpdatePopoverView.swift
@@ -1,4 +1,5 @@
import SwiftUI
+import Sparkle
/// A popover view that displays detailed update information and action buttons.
///
@@ -8,9 +9,6 @@ struct UpdatePopoverView: View {
/// The update view model that provides the current state and information
@ObservedObject var model: UpdateViewModel
- /// The actions that can be performed on updates
- let actions: UpdateUIActions
-
/// Environment value for dismissing the popover
@Environment(\.dismiss) private var dismiss
@@ -18,41 +16,47 @@ struct UpdatePopoverView: View {
VStack(alignment: .leading, spacing: 0) {
switch model.state {
case .idle:
+ // Shouldn't happen in a well-formed view stack. Higher levels
+ // should not call the popover for idles.
EmptyView()
- case .permissionRequest:
- permissionRequestView
+ case .permissionRequest(let request):
+ PermissionRequestView(request: request, dismiss: dismiss)
- case .checking:
- checkingView
+ case .checking(let checking):
+ CheckingView(checking: checking, dismiss: dismiss)
- case .updateAvailable:
- updateAvailableView
+ case .updateAvailable(let update):
+ UpdateAvailableView(update: update, dismiss: dismiss)
- case .downloading:
- downloadingView
+ case .downloading(let download):
+ DownloadingView(download: download, dismiss: dismiss)
- case .extracting:
- extractingView
+ case .extracting(let extracting):
+ ExtractingView(extracting: extracting)
- case .readyToInstall:
- readyToInstallView
+ case .readyToInstall(let ready):
+ ReadyToInstallView(ready: ready, dismiss: dismiss)
case .installing:
- installingView
+ InstallingView()
case .notFound:
- notFoundView
+ NotFoundView(dismiss: dismiss)
- case .error:
- errorView
+ case .error(let error):
+ UpdateErrorView(error: error, dismiss: dismiss)
}
}
.frame(width: 300)
}
+}
+
+fileprivate struct PermissionRequestView: View {
+ let request: UpdateState.PermissionRequest
+ let dismiss: DismissAction
- /// View shown when requesting permission to enable automatic updates
- private var permissionRequestView: some View {
+ var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
Text("Enable automatic updates?")
@@ -66,7 +70,9 @@ struct UpdatePopoverView: View {
HStack(spacing: 8) {
Button("Not Now") {
- actions.denyAutoChecks()
+ request.reply(SUUpdatePermissionResponse(
+ automaticUpdateChecks: false,
+ sendSystemProfile: false))
dismiss()
}
.keyboardShortcut(.cancelAction)
@@ -74,7 +80,9 @@ struct UpdatePopoverView: View {
Spacer()
Button("Allow") {
- actions.allowAutoChecks()
+ request.reply(SUUpdatePermissionResponse(
+ automaticUpdateChecks: true,
+ sendSystemProfile: false))
dismiss()
}
.keyboardShortcut(.defaultAction)
@@ -83,9 +91,13 @@ struct UpdatePopoverView: View {
}
.padding(16)
}
+}
+
+fileprivate struct CheckingView: View {
+ let checking: UpdateState.Checking
+ let dismiss: DismissAction
- /// View shown while checking for updates
- private var checkingView: some View {
+ var body: some View {
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 10) {
ProgressView()
@@ -97,7 +109,7 @@ struct UpdatePopoverView: View {
HStack {
Spacer()
Button("Cancel") {
- actions.cancel()
+ checking.cancel()
dismiss()
}
.keyboardShortcut(.cancelAction)
@@ -106,47 +118,39 @@ struct UpdatePopoverView: View {
}
.padding(16)
}
+}
+
+fileprivate struct UpdateAvailableView: View {
+ let update: UpdateState.UpdateAvailable
+ let dismiss: DismissAction
- /// View shown when an update is available, displaying version and size information
- private var updateAvailableView: some View {
+ var body: some View {
VStack(alignment: .leading, spacing: 0) {
VStack(alignment: .leading, spacing: 12) {
VStack(alignment: .leading, spacing: 8) {
Text("Update Available")
.font(.system(size: 13, weight: .semibold))
- if let details = model.details {
- VStack(alignment: .leading, spacing: 4) {
- HStack(spacing: 6) {
- Text("Version:")
- .foregroundColor(.secondary)
- .frame(width: 50, alignment: .trailing)
- Text(details.version)
- }
- .font(.system(size: 11))
-
- if let size = details.size {
- HStack(spacing: 6) {
- Text("Size:")
- .foregroundColor(.secondary)
- .frame(width: 50, alignment: .trailing)
- Text(size)
- }
- .font(.system(size: 11))
- }
+ VStack(alignment: .leading, spacing: 4) {
+ HStack(spacing: 6) {
+ Text("Version:")
+ .foregroundColor(.secondary)
+ .frame(width: 50, alignment: .trailing)
+ Text(update.appcastItem.displayVersionString)
}
+ .font(.system(size: 11))
}
}
HStack(spacing: 8) {
Button("Skip") {
- actions.skipThisVersion()
+ update.reply(.skip)
dismiss()
}
.controlSize(.small)
Button("Later") {
- actions.remindLater()
+ update.reply(.dismiss)
dismiss()
}
.controlSize(.small)
@@ -155,7 +159,7 @@ struct UpdatePopoverView: View {
Spacer()
Button("Install") {
- actions.install()
+ update.reply(.install)
dismiss()
}
.keyboardShortcut(.defaultAction)
@@ -164,36 +168,22 @@ struct UpdatePopoverView: View {
}
}
.padding(16)
-
- if model.details?.notesSummary != nil {
- Divider()
-
- Button(action: actions.showReleaseNotes) {
- HStack {
- Text("View Release Notes")
- .font(.system(size: 11))
- Spacer()
- Image(systemName: "arrow.up.right.square")
- .font(.system(size: 11))
- }
- .contentShape(Rectangle())
- }
- .buttonStyle(.plain)
- .padding(.horizontal, 16)
- .padding(.vertical, 10)
- .background(Color(nsColor: .controlBackgroundColor))
- }
}
}
+}
+
+fileprivate struct DownloadingView: View {
+ let download: UpdateState.Downloading
+ let dismiss: DismissAction
- /// View shown while downloading an update, with progress indicator
- private var downloadingView: some View {
+ var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
Text("Downloading Update")
.font(.system(size: 13, weight: .semibold))
- if let progress = model.progress {
+ if let expectedLength = download.expectedLength, expectedLength > 0 {
+ let progress = Double(download.progress) / Double(expectedLength)
VStack(alignment: .leading, spacing: 6) {
ProgressView(value: progress)
Text(String(format: "%.0f%%", progress * 100))
@@ -209,7 +199,7 @@ struct UpdatePopoverView: View {
HStack {
Spacer()
Button("Cancel") {
- actions.cancel()
+ download.cancel()
dismiss()
}
.keyboardShortcut(.cancelAction)
@@ -218,45 +208,45 @@ struct UpdatePopoverView: View {
}
.padding(16)
}
+}
+
+fileprivate struct ExtractingView: View {
+ let extracting: UpdateState.Extracting
- /// View shown while extracting/preparing the downloaded update
- private var extractingView: some View {
+ var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Preparing Update")
.font(.system(size: 13, weight: .semibold))
- if let progress = model.progress {
- VStack(alignment: .leading, spacing: 6) {
- ProgressView(value: progress)
- Text(String(format: "%.0f%%", progress * 100))
- .font(.system(size: 11))
- .foregroundColor(.secondary)
- }
- } else {
- ProgressView()
- .controlSize(.small)
+ VStack(alignment: .leading, spacing: 6) {
+ ProgressView(value: extracting.progress, total: 1.0)
+ Text(String(format: "%.0f%%", extracting.progress * 100))
+ .font(.system(size: 11))
+ .foregroundColor(.secondary)
}
}
.padding(16)
}
+}
+
+fileprivate struct ReadyToInstallView: View {
+ let ready: UpdateState.ReadyToInstall
+ let dismiss: DismissAction
- /// View shown when an update is ready to be installed
- private var readyToInstallView: some View {
+ var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
Text("Ready to Install")
.font(.system(size: 13, weight: .semibold))
- if let details = model.details {
- Text("Version \(details.version) is ready to install.")
- .font(.system(size: 11))
- .foregroundColor(.secondary)
- }
+ Text("The update is ready to install.")
+ .font(.system(size: 11))
+ .foregroundColor(.secondary)
}
HStack(spacing: 8) {
Button("Later") {
- actions.remindLater()
+ ready.reply(.dismiss)
dismiss()
}
.keyboardShortcut(.cancelAction)
@@ -265,7 +255,7 @@ struct UpdatePopoverView: View {
Spacer()
Button("Install and Relaunch") {
- actions.install()
+ ready.reply(.install)
dismiss()
}
.keyboardShortcut(.defaultAction)
@@ -275,9 +265,10 @@ struct UpdatePopoverView: View {
}
.padding(16)
}
-
- /// View shown during the installation process
- private var installingView: some View {
+}
+
+fileprivate struct InstallingView: View {
+ var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 10) {
ProgressView()
@@ -292,9 +283,12 @@ struct UpdatePopoverView: View {
}
.padding(16)
}
+}
+
+fileprivate struct NotFoundView: View {
+ let dismiss: DismissAction
- /// View shown when no updates are found (already on latest version)
- private var notFoundView: some View {
+ var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
Text("No Updates Found")
@@ -309,7 +303,6 @@ struct UpdatePopoverView: View {
HStack {
Spacer()
Button("OK") {
- actions.remindLater()
dismiss()
}
.keyboardShortcut(.defaultAction)
@@ -318,30 +311,31 @@ struct UpdatePopoverView: View {
}
.padding(16)
}
+}
+
+fileprivate struct UpdateErrorView: View {
+ let error: UpdateState.Error
+ let dismiss: DismissAction
- /// View shown when an error occurs during the update process
- private var errorView: some View {
+ var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 8) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
.font(.system(size: 13))
- Text(model.error?.title ?? "Update Failed")
+ Text("Update Failed")
.font(.system(size: 13, weight: .semibold))
}
- if let message = model.error?.message {
- Text(message)
- .font(.system(size: 11))
- .foregroundColor(.secondary)
- .fixedSize(horizontal: false, vertical: true)
- }
+ Text(error.error.localizedDescription)
+ .font(.system(size: 11))
+ .foregroundColor(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
}
HStack(spacing: 8) {
Button("OK") {
- actions.remindLater()
dismiss()
}
.keyboardShortcut(.cancelAction)
@@ -350,7 +344,7 @@ struct UpdatePopoverView: View {
Spacer()
Button("Retry") {
- actions.retry()
+ error.retry()
dismiss()
}
.keyboardShortcut(.defaultAction)