Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect refactored update flow to the new release notes page #3411

Open
wants to merge 8 commits into
base: anh/refactor/sparkle-user-driver
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14335,8 +14335,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 199.4.0;
kind = revision;
revision = d40a17055e1b67380886874f64daa882bbc7da0c;
};
};
9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,15 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/BrowserServicesKit",
"state" : {
"revision" : "f1a033cc4b97ab6b4d845815e825ba179c02635a",
"version" : "199.4.0"
"revision" : "d40a17055e1b67380886874f64daa882bbc7da0c"
}
},
{
"identity" : "content-scope-scripts",
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/content-scope-scripts",
"state" : {
"revision" : "1ed569676555d493c9c5575eaed22aa02569aac9",
"version" : "6.19.0"
"revision" : "32fa43a6ee861cb782771601f6885fb95006c02f"
}
},
{
Expand Down
3 changes: 2 additions & 1 deletion DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1234,7 +1234,8 @@ struct UserText {
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 triggering restart and update of the application.")
static let restartToUpdate = NSLocalizedString("settings.restart.to.update", value: "Restart to Update", comment: "Button label triggering restart and update of the application.")
static let runUpdate = NSLocalizedString("settings.run.update", value: "Update DuckDuckGo", comment: "Button label triggering 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")
Expand Down
14 changes: 13 additions & 1 deletion DuckDuckGo/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -56297,7 +56297,7 @@
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Update DuckDuckGo"
"value" : "Restart to Update"
}
},
"es" : {
Expand Down Expand Up @@ -56356,6 +56356,18 @@
}
}
},
"settings.run.update" : {
"comment" : "Button label triggering update of the application.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Update DuckDuckGo"
}
}
}
},
"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
18 changes: 0 additions & 18 deletions DuckDuckGo/Preferences/Model/AboutPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,11 @@ final class AboutPreferences: ObservableObject, PreferencesTabOpening {
static let shared = AboutPreferences()

#if SPARKLE
enum UpdateState {

case upToDate
case updateCycle(UpdateCycleProgress)

init(from update: Update?, progress: UpdateCycleProgress) {
if let update, !update.isInstalled {
self = .updateCycle(progress)
} else if progress.isFailed {
self = .updateCycle(progress)
} else {
self = .upToDate
}
}
}

@Published var updateState = UpdateState.upToDate

#if SPARKLE
var updateController: UpdateControllerProtocol? {
return Application.appDelegate.updateController
}
#endif

var areAutomaticUpdatesEnabled: Bool {
get {
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Preferences/View/PreferencesAboutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ extension Preferences {
.buttonStyle(UpdateButtonStyle(enabled: true))
case .updateCycle(let progress):
if hasPendingUpdate {
Button(UserText.runUpdate) {
Button(model.areAutomaticUpdatesEnabled ? UserText.restartToUpdate : UserText.runUpdate) {
model.runUpdate()
}
.buttonStyle(UpdateButtonStyle(enabled: true))
Expand Down
101 changes: 72 additions & 29 deletions DuckDuckGo/Updates/ReleaseNotesTabExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ protocol ReleaseNotesUserScriptProvider {
extension UserScripts: ReleaseNotesUserScriptProvider {}

public struct ReleaseNotesValues: Codable {
enum Status: String {
case loaded
case loading
case updateReady
case updateDownloading
case updatePreparing
case updateError
}

let status: String
let currentVersion: String
Expand All @@ -40,7 +48,8 @@ public struct ReleaseNotesValues: Codable {
let releaseTitle: String?
let releaseNotes: [String]?
let releaseNotesPrivacyPro: [String]?

let downloadProgress: Double?
let automaticUpdate: Bool?
}

final class ReleaseNotesTabExtension: NavigationResponder {
Expand Down Expand Up @@ -115,49 +124,83 @@ extension TabExtensions {

extension ReleaseNotesValues {

init(status: String,
init(status: Status,
currentVersion: String,
lastUpdate: UInt) {
self.init(status: status,
currentVersion: currentVersion,
latestVersion: nil,
lastUpdate: lastUpdate,
releaseTitle: nil,
releaseNotes: nil,
releaseNotesPrivacyPro: nil)
latestVersion: String? = nil,
lastUpdate: UInt,
releaseTitle: String? = nil,
releaseNotes: [String]? = nil,
releaseNotesPrivacyPro: [String]? = nil,
downloadProgress: Double? = nil,
automaticUpdate: Bool? = nil) {
self.status = status.rawValue
self.currentVersion = currentVersion
self.latestVersion = latestVersion
self.lastUpdate = lastUpdate
self.releaseTitle = releaseTitle
self.releaseNotes = releaseNotes
self.releaseNotesPrivacyPro = releaseNotesPrivacyPro
self.downloadProgress = downloadProgress
self.automaticUpdate = automaticUpdate
}

init(from updateController: UpdateController?) {
let currentVersion = "\(AppVersion().versionNumber) (\(AppVersion().buildNumber))"
let lastUpdate = UInt((updateController?.lastUpdateCheckDate ?? Date()).timeIntervalSince1970)
let status: String
let latestVersion: String

guard let updateController, updateController.updateProgress.isIdle else {
self.init(status: "loading",
guard let updateController, let latestUpdate = updateController.latestUpdate else {
self.init(status: updateController?.updateProgress.toStatus ?? .loaded,
currentVersion: currentVersion,
lastUpdate: lastUpdate)
return
}

if let latestUpdate = updateController.latestUpdate {
status = latestUpdate.isInstalled ? "loaded" : "updateReady"
latestVersion = "\(latestUpdate.version) (\(latestUpdate.build))"
self.init(status: status,
currentVersion: currentVersion,
latestVersion: latestVersion,
lastUpdate: lastUpdate,
releaseTitle: latestUpdate.title,
releaseNotes: latestUpdate.releaseNotes,
releaseNotesPrivacyPro: latestUpdate.releaseNotesPrivacyPro)
return
} else {
self.init(status: "loaded",
currentVersion: currentVersion,
lastUpdate: lastUpdate)
let updateState = UpdateState(from: updateController.latestUpdate, progress: updateController.updateProgress)

let status: Status
let downloadProgress: Double?
switch updateState {
case .upToDate:
status = .loaded
downloadProgress = nil
case .updateCycle(let progress):
status = updateController.hasPendingUpdate ? .updateReady : progress.toStatus
downloadProgress = progress.toDownloadProgress
}

self.init(status: status,
currentVersion: currentVersion,
latestVersion: latestUpdate.versionString,
lastUpdate: lastUpdate,
releaseTitle: latestUpdate.title,
releaseNotes: latestUpdate.releaseNotes,
releaseNotesPrivacyPro: latestUpdate.releaseNotesPrivacyPro,
downloadProgress: downloadProgress,
automaticUpdate: updateController.areAutomaticUpdatesEnabled)
}
}

private extension Update {
var versionString: String? {
"\(version) \(build)"
}
}

private extension UpdateCycleProgress {
var toStatus: ReleaseNotesValues.Status {
switch self {
case .updateCycleDidStart: return .loading
case .downloadDidStart, .downloading: return .updateDownloading
case .extractionDidStart, .extracting, .readyToInstallAndRelaunch, .installationDidStart, .installing: return .updatePreparing
case .updaterError: return .updateError
case .updateCycleNotStarted, .updateCycleDone: return .updateReady
}
}

var toDownloadProgress: Double? {
guard case .downloading(let percentage) = self else { return nil }
return percentage
}
}

#else
Expand Down
12 changes: 11 additions & 1 deletion DuckDuckGo/Updates/ReleaseNotesUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ final class ReleaseNotesUserScript: NSObject, Subfeature {
case reportPageException
case reportInitException
case browserRestart
case retryUpdate
}

override init() {
Expand All @@ -57,7 +58,8 @@ final class ReleaseNotesUserScript: NSObject, Subfeature {
.initialSetup: initialSetup,
.reportPageException: reportPageException,
.reportInitException: reportInitException,
.browserRestart: browserRestart
.browserRestart: browserRestart,
.retryUpdate: retryUpdate,
]

@MainActor
Expand Down Expand Up @@ -108,6 +110,14 @@ extension ReleaseNotesUserScript {
return InitialSetupResult(env: env, locale: Locale.current.identifier)
}

@MainActor
private func retryUpdate(params: Any, original: WKScriptMessage) async throws -> Encodable? {
DispatchQueue.main.async { [weak self] in
self?.updateController.checkForUpdateIfNeeded()
}
return nil
}

struct InitialSetupResult: Encodable {
let env: String
let locale: String
Expand Down
2 changes: 2 additions & 0 deletions DuckDuckGo/Updates/UpdateController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ final class UpdateController: NSObject, UpdateControllerProtocol {
updater?.automaticallyChecksForUpdates = false
updater?.automaticallyDownloadsUpdates = false
updater?.updateCheckInterval = 0
#else
checkForUpdateIfNeeded()
#endif
}

Expand Down
15 changes: 15 additions & 0 deletions DuckDuckGo/Updates/UpdateUserDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ import os.log

#if SPARKLE

enum UpdateState {
case upToDate
case updateCycle(UpdateCycleProgress)

init(from update: Update?, progress: UpdateCycleProgress) {
if let update, !update.isInstalled {
self = .updateCycle(progress)
} else if progress.isFailed {
self = .updateCycle(progress)
} else {
self = .upToDate
}
}
}

enum UpdateCycleProgress {
case updateCycleNotStarted
case updateCycleDidStart
Expand Down
Loading