Skip to content

Commit

Permalink
Design improvements to automatic update flow (#3344)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1201037661562251/1208183525704010/f
Tech Design URL:
CC:
Figma:
https://www.figma.com/design/vDBpSFDzW0eN7nwpS95gju/macOS-update-process?node-id=2359-52971&t=KnvIdm2f7PRjNwfK-4

**Description**:

**Steps to test this PR**:
1. Change the appcast URL to an invalid URL to test the error state
2. Play with different update options to see if update works

<!--
Tagging instructions
If this PR isn't ready to be merged for whatever reason it should be
marked with the `DO NOT MERGE` label (particularly if it's a draft)
If it's pending Product Review/PFR, please add the `Pending Product
Review` label.

If at any point it isn't actively being worked on/ready for
review/otherwise moving forward (besides the above PR/PFR exception)
strongly consider closing it (or not opening it in the first place). If
you decide not to close it, make sure it's labelled to make it clear the
PRs state and comment with more information.
-->

**Definition of Done**:

* [ ] Does this PR satisfy our [Definition of
Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)?

---
###### Internal references:
[Pull Request Review
Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f)
[Software Engineering
Expectations](https://app.asana.com/0/59792373528535/199064865822552)
[Technical Design
Template](https://app.asana.com/0/59792373528535/184709971311943)
[Pull Request
Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f)
  • Loading branch information
quanganhdo authored Oct 7, 2024
1 parent 3f91338 commit 7884731
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 54 deletions.
6 changes: 5 additions & 1 deletion DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1210,10 +1210,14 @@ struct UserText {
static let automaticUpdates = NSLocalizedString("settings.automatic.updates", value: "Automatically install updates (recommended)", comment: "Title of the checkbox item to set up automatic updates of the browser")
static let manualUpdates = NSLocalizedString("settings.manual.updates", value: "Check for updates but let you choose to install them", comment: "Title of the checkbox item to set up manual updates of the browser")
static let checkingForUpdate = NSLocalizedString("settings.checking.for.update", value: "Checking for update", comment: "Label informing users the app is currently checking for new update")
static let downloadingUpdate = NSLocalizedString("settings.downloading.update", value: "Downloading update %@", comment: "Label informing users the app is currently downloading the update. This will contain a percentage")
static let preparingUpdate = NSLocalizedString("settings.preparing.update", value: "Preparing update", comment: "Label informing users the app is preparing to update.")
static let updateFailed = NSLocalizedString("settings.update.failed", value: "Update failed", comment: "Label informing users the app is unable to update.")
static let upToDate = NSLocalizedString("settings.up.to.date", value: "DuckDuckGo is up to date", comment: "Label informing users the app is currently up to date and no update is required.")
static let newerVersionAvailable = NSLocalizedString("settings.newer.version.available", value: "Newer version available", comment: "Label informing users the newer version of the app is available to install.")
static let lastChecked = NSLocalizedString("settings.last.checked", value: "Last checked", comment: "Label informing users what is the last time the app checked for the update.")
static let runUpdate = NSLocalizedString("settings.restart.to.update", value: "Update DuckDuckGo", comment: "Button label trigering restart and update of the application.")
static let runUpdate = NSLocalizedString("settings.restart.to.update", value: "Update DuckDuckGo", comment: "Button label triggering restart and update of the application.")
static let retryUpdate = NSLocalizedString("settings.retry.update", value: "Retry Update", comment: "Button label triggering a retry of the update.")
static let browserUpdatedNotification = NSLocalizedString("notification.browser.updated", value: "Browser Updated", comment: "Notification informing user the app has been updated")
static let browserDowngradedNotification = NSLocalizedString("notification.browser.downgraded", value: "Browser Downgraded", comment: "Notification informing user the app has been downgraded")
static let criticalUpdateNotification = NSLocalizedString("notification.critical.update", value: "Critical update required. Restart to update.", comment: "Notification informing user a critical update is required.")
Expand Down
78 changes: 49 additions & 29 deletions DuckDuckGo/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,6 @@
}
}
}
},
" — Downloading %@ / %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : " — Downloading %1$@ / %2$@"
}
}
}
},
" — Downloading update" : {

},
" — Extracting update" : {

},
" — Extracting update (%@%%)" : {

},
" — Installing" : {

},
" — Ready to install" : {

},
" %@" : {
"localizations" : {
Expand Down Expand Up @@ -12408,9 +12383,6 @@
}
}
}
},
"Checking for update" : {

},
"clear" : {
"comment" : "Clear button",
Expand Down Expand Up @@ -55016,6 +54988,18 @@
}
}
},
"settings.downloading.update" : {
"comment" : "Label informing users the app is currently downloading the update. This will contain a percentage",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Downloading update %@"
}
}
}
},
"settings.last.checked" : {
"comment" : "Label informing users what is the last time the app checked for the update.",
"extractionState" : "extracted_with_value",
Expand Down Expand Up @@ -55196,8 +55180,20 @@
}
}
},
"settings.preparing.update" : {
"comment" : "Label informing users the app is preparing to update.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Preparing update"
}
}
}
},
"settings.restart.to.update" : {
"comment" : "Button label trigering restart and update of the application.",
"comment" : "Button label triggering restart and update of the application.",
"extractionState" : "extracted_with_value",
"localizations" : {
"de" : {
Expand Down Expand Up @@ -55256,6 +55252,18 @@
}
}
},
"settings.retry.update" : {
"comment" : "Button label triggering a retry of the update.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Retry Update"
}
}
}
},
"settings.up.to.date" : {
"comment" : "Label informing users the app is currently up to date and no update is required.",
"extractionState" : "extracted_with_value",
Expand Down Expand Up @@ -55316,6 +55324,18 @@
}
}
},
"settings.update.failed" : {
"comment" : "Label informing users the app is unable to update.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Update failed"
}
}
}
},
"share.menu.item" : {
"comment" : "Menu item title",
"extractionState" : "extracted_with_value",
Expand Down
2 changes: 2 additions & 0 deletions DuckDuckGo/Preferences/Model/AboutPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ final class AboutPreferences: ObservableObject, PreferencesTabOpening {
init(from update: Update?, progress: UpdateCycleProgress) {
if let update, !update.isInstalled {
self = .updateCycle(progress)
} else if progress.isFailed {
self = .updateCycle(progress)
} else {
self = .upToDate
}
Expand Down
38 changes: 20 additions & 18 deletions DuckDuckGo/Preferences/View/PreferencesAboutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ extension Preferences {
}

#if SPARKLE
private var formatter: ByteCountFormatter {
let formatter = ByteCountFormatter()
formatter.allowsNonnumericFormatting = false
formatter.allowedUnits = [.useKB, .useMB, .useGB]
private var formatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.maximumFractionDigits = 0
return formatter
}

Expand All @@ -188,17 +188,14 @@ extension Preferences {
case .updateCycleDidStart:
Text(" — " + UserText.checkingForUpdate)
case .downloadDidStart:
Text(" — Downloading update")
case .downloading(let bytesDownloaded, let bytesToDownload):
Text(" — Downloading \(formatter.string(fromByteCount: Int64(bytesDownloaded))) / \(formatter.string(fromByteCount: Int64(bytesToDownload)))")
case .extractionDidStart:
Text(" — Extracting update")
case .extracting(let percentage):
Text(" — Extracting update (\(String(format: "%.1f", percentage * 100))%)")
case .readyToInstallAndRelaunch:
Text(" — Ready to install")
case .installationDidStart, .installing:
Text(" — Installing")
Text("" + String(format: UserText.downloadingUpdate, ""))
case .downloading(let percentage):
Text("" + String(format: UserText.downloadingUpdate,
formatter.string(from: NSNumber(value: percentage)) ?? ""))
case .extractionDidStart, .extracting, .readyToInstallAndRelaunch, .installationDidStart, .installing:
Text("" + UserText.preparingUpdate)
case .updaterError:
Text(" — " + UserText.updateFailed)
case .updateCycleNotStarted, .updateCycleDone:
EmptyView()
}
Expand All @@ -210,8 +207,8 @@ extension Preferences {
case .upToDate:
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
case .updateCycle:
if hasPendingUpdate {
case .updateCycle(let progress):
if hasPendingUpdate || progress.isFailed {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(.red)
} else {
Expand Down Expand Up @@ -249,12 +246,17 @@ extension Preferences {
model.checkForUpdate()
}
.buttonStyle(UpdateButtonStyle(enabled: true))
case .updateCycle:
case .updateCycle(let progress):
if hasPendingUpdate {
Button(UserText.runUpdate) {
model.runUpdate()
}
.buttonStyle(UpdateButtonStyle(enabled: true))
} else if progress.isFailed {
Button(UserText.retryUpdate) {
model.checkForUpdate()
}
.buttonStyle(UpdateButtonStyle(enabled: true))
} else {
Button(UserText.checkForUpdate) {
model.checkForUpdate()
Expand Down
8 changes: 6 additions & 2 deletions DuckDuckGo/Updates/UpdateController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,12 @@ extension UpdateController: SPUUpdaterDelegate {
}

func updater(_ updater: SPUUpdater, didFinishUpdateCycleFor updateCheck: SPUUpdateCheck, error: (any Error)?) {
Logger.updates.debug("Updater did finish update cycle")
updateProgress = .updateCycleDone
if error == nil {
Logger.updates.debug("Updater did finish update cycle")
updateProgress = .updateCycleDone
} else {
Logger.updates.debug("Updater did finish update cycle with error")
}
}

}
Expand Down
18 changes: 14 additions & 4 deletions DuckDuckGo/Updates/UpdateUserDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ enum UpdateCycleProgress {
case updateCycleDidStart
case updateCycleDone
case downloadDidStart
case downloading(UInt64, UInt64)
case downloading(Double)
case extractionDidStart
case extracting(Double)
case readyToInstallAndRelaunch
case installationDidStart
case installing
case updaterError(Error)

static var `default` = UpdateCycleProgress.updateCycleNotStarted

Expand All @@ -48,7 +49,14 @@ enum UpdateCycleProgress {

var isIdle: Bool {
switch self {
case .updateCycleDone, .updateCycleNotStarted: return true
case .updateCycleDone, .updateCycleNotStarted, .updaterError: return true
default: return false
}
}

var isFailed: Bool {
switch self {
case .updaterError: return true
default: return false
}
}
Expand Down Expand Up @@ -128,7 +136,8 @@ final class UpdateUserDriver: NSObject, SPUUserDriver {
}

func showUpdaterError(_ error: any Error, acknowledgement: @escaping () -> Void) {
// no-op
updateProgress = .updaterError(error)
acknowledgement()
}

func showDownloadInitiated(cancellation: @escaping () -> Void) {
Expand All @@ -145,7 +154,7 @@ final class UpdateUserDriver: NSObject, SPUUserDriver {
if bytesDownloaded > bytesToDownload {
bytesToDownload = bytesDownloaded
}
updateProgress = .downloading(bytesDownloaded, bytesToDownload)
updateProgress = .downloading(Double(bytesDownloaded) / Double(bytesToDownload))
}

func showDownloadDidStartExtractingUpdate() {
Expand Down Expand Up @@ -182,6 +191,7 @@ final class UpdateUserDriver: NSObject, SPUUserDriver {
}

func dismissUpdateInstallation() {
guard !updateProgress.isFailed else { return }
updateProgress = .updateCycleDone
}
}
Expand Down

0 comments on commit 7884731

Please sign in to comment.