From c7eb27860ba5eb954766ea7c499bea80f3caeec4 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Mon, 25 Mar 2024 08:46:28 +0100 Subject: [PATCH 01/22] Settings model --- .../xcshareddata/swiftpm/Package.resolved | 10 +++--- DuckDuckGo/Common/Localizables/UserText.swift | 3 ++ .../Utilities/UserDefaultsWrapper.swift | 1 + DuckDuckGo/Localizable.xcstrings | 36 +++++++++++++++++++ .../Model/DataClearingPreferences.swift | 12 +++++++ .../View/PreferencesDataClearingView.swift | 19 +++++++++- 6 files changed, 75 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 69249300a1..2a0afc1938 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "branch" : "anh/pp/add-metadata", - "revision" : "0cba47cf651175806bc151366605f8b19802d3ee" + "revision" : "918c7ef70f70ca2c62ef781dd6aae8d53cf15db4", + "version" : "129.1.6" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "edd96481d49b094c260f9a79e078abdbdd3a83fb", - "version" : "5.6.0" + "revision" : "2f44185cca2edefbae7557393a61a23c282abbf8", + "version" : "5.7.0" } }, { @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 66e81e8608..26ce896e37 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1037,6 +1037,9 @@ struct UserText { static let fireproofCheckboxTitle = NSLocalizedString("fireproof.checkbox.title", value: "Ask to Fireproof websites when signing in", comment: "Fireproof settings checkbox title") static let fireproofExplanation = NSLocalizedString("fireproof.explanation", value: "When you Fireproof a site, cookies won't be erased and you'll stay signed in, even after using the Fire Button.", comment: "Fireproofing mechanism explanation") static let manageFireproofSites = NSLocalizedString("fireproof.manage-sites", value: "Manage Fireproof Sites…", comment: "Fireproof settings button caption") + static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically Clear Data", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.") + static let burnDataOnQuit = NSLocalizedString("burn.data.on.quit", value: "Burn data on quit", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") + static let burnDataOnQuitExplanation = NSLocalizedString("burn.data.on.quit.explanation", value: "Clear all browsing data, history, and cookies whenever you close the browser. Cookies for Fireproof Sites won't be erased.", comment: "Explanation of a setting which configures clearing data automatically after quitting the app.") // MARK: Crash Report static let crashReportTitle = NSLocalizedString("crash-report.title", value: "DuckDuckGo Privacy Browser quit unexpectedly.", comment: "Title of the dialog where the user can send a crash report") diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index e11769ae4e..37311af671 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -54,6 +54,7 @@ public struct UserDefaultsWrapper { case grammarCheckEnabledOnce = "grammar.check.enabled.once" case loginDetectionEnabled = "fireproofing.login-detection-enabled" + case burnDataOnQuitEnabled = "preferences.burn-data-on-quit-enabled" case gpcEnabled = "preferences.gpc-enabled" case selectedDownloadLocationKey = "preferences.download-location" case lastUsedCustomDownloadLocation = "preferences.custom-last-used-download-location" diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index ce218cd1b2..8ee1833b17 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -5478,6 +5478,18 @@ } } }, + "automatically.clear.data" : { + "comment" : "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Automatically Clear Data" + } + } + } + }, "Birthday" : { "comment" : "Title of the section of the Identities manager where the user can add/modify a date of birth", "localizations" : { @@ -9747,6 +9759,30 @@ } } }, + "burn.data.on.quit" : { + "comment" : "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Burn data on quit" + } + } + } + }, + "burn.data.on.quit.explanation" : { + "comment" : "Explanation of a setting which configures clearing data automatically after quitting the app.", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Clear all browsing data, history, and cookies whenever you close the browser. Cookies for Fireproof Sites won't be erased." + } + } + } + }, "burner.homepage.description.1" : { "comment" : "Descriptions of features Fire page. Provides information about browsing functionalities such as browsing without saving local history, signing in to a site with a different account, and troubleshooting websites.", "extractionState" : "extracted_with_value", diff --git a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift index e336ab7cbd..7e9fa53059 100644 --- a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift @@ -29,6 +29,13 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { } } + @Published + var isBurnDataOnQuitEnabled: Bool { + didSet { + persistor.burnDataOnQuitEnabled = isBurnDataOnQuitEnabled + } + } + @MainActor func presentManageFireproofSitesDialog() { let fireproofDomainsWindowController = FireproofDomainsViewController.create().wrappedInWindowController() @@ -46,6 +53,7 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { init(persistor: FireButtonPreferencesPersistor = FireButtonPreferencesUserDefaultsPersistor()) { self.persistor = persistor isLoginDetectionEnabled = persistor.loginDetectionEnabled + isBurnDataOnQuitEnabled = persistor.burnDataOnQuitEnabled } private var persistor: FireButtonPreferencesPersistor @@ -53,6 +61,7 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { protocol FireButtonPreferencesPersistor { var loginDetectionEnabled: Bool { get set } + var burnDataOnQuitEnabled: Bool { get set } } struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersistor { @@ -60,4 +69,7 @@ struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersisto @UserDefaultsWrapper(key: .loginDetectionEnabled, defaultValue: false) var loginDetectionEnabled: Bool + @UserDefaultsWrapper(key: .burnDataOnQuitEnabled, defaultValue: false) + var burnDataOnQuitEnabled: Bool + } diff --git a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift index dc3f28607c..094fec3f83 100644 --- a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift @@ -28,7 +28,24 @@ extension Preferences { var body: some View { PreferencePane(UserText.dataClearing) { - // SECTION 1: Fireproof Site + // SECTION 1: Automatically Clear Data + PreferencePaneSection(UserText.automaticallyClearData) { + + PreferencePaneSubSection { + ToggleMenuItem(UserText.burnDataOnQuit, isOn: $model.isBurnDataOnQuitEnabled) + VStack(alignment: .leading, spacing: 1) { + TextMenuItemCaption(UserText.burnDataOnQuitExplanation) + } + } + + PreferencePaneSubSection { + Button(UserText.manageFireproofSites) { + model.presentManageFireproofSitesDialog() + } + } + } + + // SECTION 2: Fireproof Site PreferencePaneSection(UserText.fireproofSites) { PreferencePaneSubSection { From 30a50f0d4f06dd76835dc130cc616d7e6fc383fc Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Mon, 25 Mar 2024 09:04:22 +0100 Subject: [PATCH 02/22] Disabling restorePreviousSession in case burn data on quit is enabled --- .../Model/DataClearingPreferences.swift | 8 ++++++++ .../Preferences/Model/StartupPreferences.swift | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift index 7e9fa53059..1f96105a1e 100644 --- a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift @@ -20,6 +20,7 @@ import Foundation final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { + static let burnOnQuitNotificationKey = "isBurnDataOnQuitEnabled" static let shared = DataClearingPreferences() @Published @@ -33,6 +34,9 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { var isBurnDataOnQuitEnabled: Bool { didSet { persistor.burnDataOnQuitEnabled = isBurnDataOnQuitEnabled + NotificationCenter.default.post(name: .burnDataOnQuitDidChange, + object: nil, + userInfo: [Self.burnOnQuitNotificationKey: isBurnDataOnQuitEnabled]) } } @@ -73,3 +77,7 @@ struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersisto var burnDataOnQuitEnabled: Bool } + +extension Notification.Name { + static let burnDataOnQuitDidChange = Notification.Name("burnDataOnQuitDidChange") +} diff --git a/DuckDuckGo/Preferences/Model/StartupPreferences.swift b/DuckDuckGo/Preferences/Model/StartupPreferences.swift index 6e46f9b729..834bc45152 100644 --- a/DuckDuckGo/Preferences/Model/StartupPreferences.swift +++ b/DuckDuckGo/Preferences/Model/StartupPreferences.swift @@ -46,6 +46,7 @@ final class StartupPreferences: ObservableObject { private let pinningManager: LocalPinningManager private var persistor: StartupPreferencesPersistor private var pinnedViewsNotificationCancellable: AnyCancellable? + private var dataClearingPreferencesNotificationCancellable: AnyCancellable? init(pinningManager: LocalPinningManager = LocalPinningManager.shared, persistor: StartupPreferencesPersistor = StartupPreferencesUserDefaultsPersistor(appearancePrefs: AppearancePreferences.shared)) { @@ -56,6 +57,7 @@ final class StartupPreferences: ObservableObject { customHomePageURL = persistor.customHomePageURL updateHomeButtonState() listenToPinningManagerNotifications() + listenToDataClearingPreferencesNotifications() } @Published var restorePreviousSession: Bool { @@ -129,6 +131,18 @@ final class StartupPreferences: ObservableObject { } } + private func listenToDataClearingPreferencesNotifications() { + dataClearingPreferencesNotificationCancellable = NotificationCenter.default.publisher(for: .burnDataOnQuitDidChange).sink { [weak self] notification in + guard let self = self, + let isBurnDataOnQuitEnabled = notification.userInfo?[DataClearingPreferences.burnOnQuitNotificationKey] as? Bool else { + return + } + if isBurnDataOnQuitEnabled { + self.restorePreviousSession = false + } + } + } + private func urlWithScheme(_ urlString: String) -> String? { guard var urlWithScheme = urlString.url else { return nil From 6298998b6cc37389a422d126743cd5bddf7d6c56 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Mon, 25 Mar 2024 09:59:49 +0100 Subject: [PATCH 03/22] Label: Disable the Automatically Clear Data setting to enable session restore on startup --- DuckDuckGo/Common/Localizables/UserText.swift | 12 +++ DuckDuckGo/Localizable.xcstrings | 24 ++++++ .../Preferences/Model/SearchPreferences.swift | 5 ++ .../Model/StartupPreferences.swift | 2 +- .../View/PreferencesGeneralView.swift | 10 +++ .../View/PreferencesRootView.swift | 3 +- .../Assets.xcassets/Colors/Contents.json | 6 ++ .../LinkBlueColor.colorset/Contents.json | 78 +++++++++++++++++++ 8 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 LocalPackages/SwiftUIExtensions/Sources/PreferencesViews/Assets.xcassets/Colors/Contents.json create mode 100644 LocalPackages/SwiftUIExtensions/Sources/PreferencesViews/Assets.xcassets/Colors/LinkBlueColor.colorset/Contents.json diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 26ce896e37..3ab373152e 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1040,6 +1040,18 @@ struct UserText { static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically Clear Data", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.") static let burnDataOnQuit = NSLocalizedString("burn.data.on.quit", value: "Burn data on quit", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") static let burnDataOnQuitExplanation = NSLocalizedString("burn.data.on.quit.explanation", value: "Clear all browsing data, history, and cookies whenever you close the browser. Cookies for Fireproof Sites won't be erased.", comment: "Explanation of a setting which configures clearing data automatically after quitting the app.") + static func disableBurnOnQuitToEnableSessionRestore() -> String { + let localized = NSLocalizedString("disable.burn.on.quit.to.enable.session.restore", + value: "Disable the %@ setting to enable session restore on startup.", + comment: "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit") + return String(format: localized, automaticallyClearData) + } + static func showDataClearingSettings() -> String { + let localized = NSLocalizedString("show.data.clearing.settings", + value: "Show %@ Settings.", + comment: "Button in Settings. It navigates user to Data Clearing Settings.") + return String(format: localized, dataClearing) + } // MARK: Crash Report static let crashReportTitle = NSLocalizedString("crash-report.title", value: "DuckDuckGo Privacy Browser quit unexpectedly.", comment: "Title of the dialog where the user can send a crash report") diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 8ee1833b17..9ff043921e 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -12547,6 +12547,18 @@ } } }, + "disable.burn.on.quit.to.enable.session.restore" : { + "comment" : "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Disable the %@ setting to enable session restore on startup." + } + } + } + }, "disable.email.protection.mesage" : { "comment" : "Message for alert shown when user disables email protection", "extractionState" : "extracted_with_value", @@ -47429,6 +47441,18 @@ } } }, + "show.data.clearing.settings" : { + "comment" : "Button in Settings. It navigates user to Data Clearing Settings.", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Show %@ Settings." + } + } + } + }, "show.folder.contents" : { "comment" : "Menu item that shows the content of a folder ", "extractionState" : "extracted_with_value", diff --git a/DuckDuckGo/Preferences/Model/SearchPreferences.swift b/DuckDuckGo/Preferences/Model/SearchPreferences.swift index bbe5cfb70c..65d385d72b 100644 --- a/DuckDuckGo/Preferences/Model/SearchPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SearchPreferences.swift @@ -61,4 +61,9 @@ extension PreferencesTabOpening { WindowControllersManager.shared.show(url: url, source: .ui, newTab: true) } + @MainActor + func show(url: URL) { + WindowControllersManager.shared.show(url: url, source: .ui, newTab: false) + } + } diff --git a/DuckDuckGo/Preferences/Model/StartupPreferences.swift b/DuckDuckGo/Preferences/Model/StartupPreferences.swift index 834bc45152..bec557d2da 100644 --- a/DuckDuckGo/Preferences/Model/StartupPreferences.swift +++ b/DuckDuckGo/Preferences/Model/StartupPreferences.swift @@ -40,7 +40,7 @@ struct StartupPreferencesUserDefaultsPersistor: StartupPreferencesPersistor { } -final class StartupPreferences: ObservableObject { +final class StartupPreferences: ObservableObject, PreferencesTabOpening { static let shared = StartupPreferences() private let pinningManager: LocalPinningManager diff --git a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift index 591961ee75..75a65a6703 100644 --- a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift @@ -28,6 +28,7 @@ extension Preferences { @ObservedObject var startupModel: StartupPreferences @ObservedObject var downloadsModel: DownloadsPreferences @ObservedObject var searchModel: SearchPreferences + @ObservedObject var dataClearingModel: DataClearingPreferences @State private var showingCustomHomePageSheet = false var body: some View { @@ -43,7 +44,16 @@ extension Preferences { Text(UserText.reopenAllWindowsFromLastSession).tag(true) }, label: {}) .pickerStyle(.radioGroup) + .disabled(dataClearingModel.isBurnDataOnQuitEnabled) .offset(x: PreferencesViews.Const.pickerHorizontalOffset) + if dataClearingModel.isBurnDataOnQuitEnabled { + VStack(alignment: .leading, spacing: 1) { + TextMenuItemCaption(UserText.disableBurnOnQuitToEnableSessionRestore()) + TextButton(UserText.showDataClearingSettings()) { + startupModel.show(url: .settingsPane(.dataClearing)) + } + } + } } } diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index 922b2fe501..c2557f8f04 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -94,7 +94,8 @@ enum Preferences { case .general: GeneralView(startupModel: StartupPreferences.shared, downloadsModel: DownloadsPreferences.shared, - searchModel: SearchPreferences.shared) + searchModel: SearchPreferences.shared, + dataClearingModel: DataClearingPreferences.shared) case .sync: SyncView() case .appearance: diff --git a/LocalPackages/SwiftUIExtensions/Sources/PreferencesViews/Assets.xcassets/Colors/Contents.json b/LocalPackages/SwiftUIExtensions/Sources/PreferencesViews/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/LocalPackages/SwiftUIExtensions/Sources/PreferencesViews/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/SwiftUIExtensions/Sources/PreferencesViews/Assets.xcassets/Colors/LinkBlueColor.colorset/Contents.json b/LocalPackages/SwiftUIExtensions/Sources/PreferencesViews/Assets.xcassets/Colors/LinkBlueColor.colorset/Contents.json new file mode 100644 index 0000000000..bb413935ee --- /dev/null +++ b/LocalPackages/SwiftUIExtensions/Sources/PreferencesViews/Assets.xcassets/Colors/LinkBlueColor.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEE", + "green" : "0x69", + "red" : "0x39" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF6", + "green" : "0x94", + "red" : "0x71" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "contrast", + "value" : "high" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.933", + "green" : "0.412", + "red" : "0.224" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + }, + { + "appearance" : "contrast", + "value" : "high" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.965", + "green" : "0.580", + "red" : "0.443" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} From b0e1c7f5aedc38643dc987e965e5a49d43ee28b5 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Wed, 27 Mar 2024 14:16:01 +0100 Subject: [PATCH 04/22] Burn on Quit functionality --- DuckDuckGo.xcodeproj/project.pbxproj | 8 ++++ DuckDuckGo/Application/AppDelegate.swift | 12 +++++ .../Application/BurnOnQuitHandler.swift | 47 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 DuckDuckGo/Application/BurnOnQuitHandler.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 12cb453fdc..63ae013877 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -28,6 +28,9 @@ 1D01A3DA2B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3D72B88DF8B00FE8150 /* PreferencesSyncView.swift */; }; 1D02633628D8A9A9005CBB41 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; settings = {COMPILER_FLAGS = "-Wno-deprecated -Wno-strict-prototypes"; }; }; 1D074B272909A433006E4AC3 /* PasswordManagerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D074B262909A433006E4AC3 /* PasswordManagerCoordinator.swift */; }; + 1D0E4F562BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */; }; + 1D0E4F572BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */; }; + 1D0E4F582BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */; }; 1D12F2E2298BC660009A65FD /* InternalUserDeciderStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D12F2E1298BC660009A65FD /* InternalUserDeciderStoreMock.swift */; }; 1D1A33492A6FEB170080ACED /* BurnerMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1A33482A6FEB170080ACED /* BurnerMode.swift */; }; 1D1A334A2A6FEB170080ACED /* BurnerMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1A33482A6FEB170080ACED /* BurnerMode.swift */; }; @@ -3469,6 +3472,7 @@ 1D02633428D8A9A9005CBB41 /* BWEncryption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BWEncryption.h; sourceTree = ""; }; 1D02633528D8A9A9005CBB41 /* BWEncryption.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BWEncryption.m; sourceTree = ""; }; 1D074B262909A433006E4AC3 /* PasswordManagerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordManagerCoordinator.swift; sourceTree = ""; }; + 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurnOnQuitHandler.swift; sourceTree = ""; }; 1D12F2E1298BC660009A65FD /* InternalUserDeciderStoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalUserDeciderStoreMock.swift; sourceTree = ""; }; 1D1A33482A6FEB170080ACED /* BurnerMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurnerMode.swift; sourceTree = ""; }; 1D1C36E229FAE8DA001FA40C /* FaviconManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconManagerTests.swift; sourceTree = ""; }; @@ -7000,6 +7004,7 @@ 858A798226A8B75F00A75A42 /* CopyHandler.swift */, 1D36E65A298ACD2900AA485D /* AppIconChanger.swift */, CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */, + 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */, ); path = Application; sourceTree = ""; @@ -10684,6 +10689,7 @@ 3706FC52293F65D500E42796 /* MouseOverAnimationButton.swift in Sources */, B60293E72BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */, 3706FC53293F65D500E42796 /* TabBarScrollView.swift in Sources */, + 1D0E4F572BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */, 3706FC54293F65D500E42796 /* BookmarkListTreeControllerDataSource.swift in Sources */, 3706FC55293F65D500E42796 /* AddressBarViewController.swift in Sources */, 3706FC56293F65D500E42796 /* Permissions.swift in Sources */, @@ -11759,6 +11765,7 @@ 4B957AF12AC7AE700062CA31 /* AppStateRestorationManager.swift in Sources */, 4B957AF22AC7AE700062CA31 /* DailyPixel.swift in Sources */, 9FDA6C232B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, + 1D0E4F582BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */, 4B957AF32AC7AE700062CA31 /* NavigationHotkeyHandler.swift in Sources */, 4B957AF42AC7AE700062CA31 /* ClickToLoadUserScript.swift in Sources */, 4B957AF52AC7AE700062CA31 /* WindowControllersManager.swift in Sources */, @@ -12171,6 +12178,7 @@ B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */, AA5FA697275F90C400DCE9C9 /* FaviconImageCache.swift in Sources */, 1430DFF524D0580F00B8978C /* TabBarViewController.swift in Sources */, + 1D0E4F562BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */, B62B483E2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, 4B92929B26670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift in Sources */, 56D145EB29E6C99B00E3488A /* DataImportStatusProviding.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index e90e95bb07..5c7e0627cd 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -71,6 +71,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel let internalUserDecider: InternalUserDecider let featureFlagger: FeatureFlagger private var appIconChanger: AppIconChanger! + private var burnOnQuitHandler: BurnOnQuitHandler! private(set) var syncDataProviders: SyncDataProviders! private(set) var syncService: DDGSyncing? @@ -307,6 +308,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #if SUBSCRIPTION #endif + + burnOnQuitHandler = BurnOnQuitHandler(preferences: .shared, fireCoordinator: FireCoordinator()) } func applicationDidBecomeActive(_ notification: Notification) { @@ -346,6 +349,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } stateRestorationManager?.applicationWillTerminate() + if burnOnQuitHandler.shouldBurnOnQuit { + burnOnQuitHandler.onBurnOnQuitCompleted = { + NSApplication.shared.reply(toApplicationShouldTerminate: true) + } + burnOnQuitHandler.burnOnQuit() + + return .terminateLater + } + return .terminateNow } diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/BurnOnQuitHandler.swift new file mode 100644 index 0000000000..459c35445b --- /dev/null +++ b/DuckDuckGo/Application/BurnOnQuitHandler.swift @@ -0,0 +1,47 @@ +// +// BurnOnQuitHandler.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +final class BurnOnQuitHandler { + + init(preferences: DataClearingPreferences, + fireCoordinator: FireCoordinator ) { + self.preferences = preferences + self.fireCoordinator = fireCoordinator + } + + private let preferences: DataClearingPreferences + private let fireCoordinator: FireCoordinator + + var shouldBurnOnQuit: Bool { + return preferences.isBurnDataOnQuitEnabled + } + + // Completion handler for all quit tasks + var onBurnOnQuitCompleted: (() -> Void)? + + @MainActor + func burnOnQuit() { + //TODO: Refactor from static + FireCoordinator.fireViewModel.fire.burnAll { [weak self] in + self?.onBurnOnQuitCompleted?() + } + } + +} From 21987477c6c24ec7651671e903ff4305244579cd Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Wed, 27 Mar 2024 16:09:17 +0100 Subject: [PATCH 05/22] Delay for testing --- DuckDuckGo/Application/BurnOnQuitHandler.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/BurnOnQuitHandler.swift index 459c35445b..f403b7b9ad 100644 --- a/DuckDuckGo/Application/BurnOnQuitHandler.swift +++ b/DuckDuckGo/Application/BurnOnQuitHandler.swift @@ -39,8 +39,11 @@ final class BurnOnQuitHandler { @MainActor func burnOnQuit() { //TODO: Refactor from static + //TODO: Without opening a new window FireCoordinator.fireViewModel.fire.burnAll { [weak self] in - self?.onBurnOnQuitCompleted?() + DispatchQueue.main.asyncAfter(deadline: .now() + 60) { + self?.onBurnOnQuitCompleted?() + } } } From b657c44db7d7b1b7823ad71622df41f90b943242 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Fri, 29 Mar 2024 16:26:51 +0100 Subject: [PATCH 06/22] Edge case: unexpected app termination --- DuckDuckGo/Application/AppDelegate.swift | 2 ++ .../Application/BurnOnQuitHandler.swift | 33 ++++++++++++++++--- .../Utilities/UserDefaultsWrapper.swift | 1 + 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 5c7e0627cd..8e1f260661 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -310,6 +310,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #endif burnOnQuitHandler = BurnOnQuitHandler(preferences: .shared, fireCoordinator: FireCoordinator()) + burnOnQuitHandler.burnOnStartIfNeeded() + burnOnQuitHandler.resetTheFlag() } func applicationDidBecomeActive(_ notification: Notification) { diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/BurnOnQuitHandler.swift index f403b7b9ad..fbe10d7cf7 100644 --- a/DuckDuckGo/Application/BurnOnQuitHandler.swift +++ b/DuckDuckGo/Application/BurnOnQuitHandler.swift @@ -17,6 +17,7 @@ // import Foundation +import Combine final class BurnOnQuitHandler { @@ -29,6 +30,8 @@ final class BurnOnQuitHandler { private let preferences: DataClearingPreferences private let fireCoordinator: FireCoordinator + // MARK: - Burn On Quit + var shouldBurnOnQuit: Bool { return preferences.isBurnDataOnQuitEnabled } @@ -38,13 +41,33 @@ final class BurnOnQuitHandler { @MainActor func burnOnQuit() { - //TODO: Refactor from static - //TODO: Without opening a new window + guard shouldBurnOnQuit else { return } + // TODO: Refactor from static FireCoordinator.fireViewModel.fire.burnAll { [weak self] in - DispatchQueue.main.asyncAfter(deadline: .now() + 60) { - self?.onBurnOnQuitCompleted?() - } + self?.burnPerformedSuccessfullyOnQuit = true + self?.onBurnOnQuitCompleted?() } } + // MARK: - Burn On Start + // In case the burn on quit wasn't successfull + + @UserDefaultsWrapper(key: .burnPerformedSuccessfullyOnQuit, defaultValue: false) + private var burnPerformedSuccessfullyOnQuit: Bool + + var shouldBurnOnStart: Bool { + return !burnPerformedSuccessfullyOnQuit + } + + func resetTheFlag() { + burnPerformedSuccessfullyOnQuit = false + } + + @MainActor + func burnOnStartIfNeeded() { + guard preferences.isBurnDataOnQuitEnabled, shouldBurnOnStart else { return } + + FireCoordinator.fireViewModel.fire.burnAll() + } + } diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 37311af671..11732df24d 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -78,6 +78,7 @@ public struct UserDefaultsWrapper { case lastCrashReportCheckDate = "last.crash.report.check.date" case fireInfoPresentedOnce = "fire.info.presented.once" + case burnPerformedSuccessfullyOnQuit = "burn.performed.successfully.on.quit" case restorePreviousSession = "preferences.startup.restore-previous-session" case launchToCustomHomePage = "preferences.startup.launch-to-custom-home-page" From d75afc19e4529ca634cad9e6f49153ac37f7585f Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Tue, 2 Apr 2024 14:34:37 +0200 Subject: [PATCH 07/22] Additional button removed --- .../Preferences/View/PreferencesDataClearingView.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift index 094fec3f83..1b886b9cf5 100644 --- a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift @@ -38,11 +38,6 @@ extension Preferences { } } - PreferencePaneSubSection { - Button(UserText.manageFireproofSites) { - model.presentManageFireproofSitesDialog() - } - } } // SECTION 2: Fireproof Site From 00631e49b707d6b0ec1479cdcac7d5e63032af41 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Mon, 8 Apr 2024 11:32:13 +0200 Subject: [PATCH 08/22] Added clearing data options and warn on burn option --- .../Application/BurnOnQuitHandler.swift | 16 ++++ .../Common/Extensions/NSAlertExtension.swift | 11 +++ DuckDuckGo/Common/Localizables/UserText.swift | 16 +++- .../Utilities/UserDefaultsWrapper.swift | 2 + DuckDuckGo/Localizable.xcstrings | 78 ++++++++++++++++++- .../Model/DataClearingPreferences.swift | 28 +++++++ .../View/PreferencesDataClearingView.swift | 14 ++++ 7 files changed, 159 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/BurnOnQuitHandler.swift index fbe10d7cf7..216a6f8521 100644 --- a/DuckDuckGo/Application/BurnOnQuitHandler.swift +++ b/DuckDuckGo/Application/BurnOnQuitHandler.swift @@ -36,12 +36,27 @@ final class BurnOnQuitHandler { return preferences.isBurnDataOnQuitEnabled } + var shouldWarnOnBurn: Bool { + return preferences.isWarnBeforeClearingEnabled + } + // Completion handler for all quit tasks var onBurnOnQuitCompleted: (() -> Void)? @MainActor func burnOnQuit() { guard shouldBurnOnQuit else { return } + + if shouldWarnOnBurn { + let alert = NSAlert.burnOnQuitAlert() + let response = alert.runModal() + guard response == .alertFirstButtonReturn else { + burnPerformedSuccessfullyOnQuit = true + onBurnOnQuitCompleted?() + return + } + } + // TODO: Refactor from static FireCoordinator.fireViewModel.fire.burnAll { [weak self] in self?.burnPerformedSuccessfullyOnQuit = true @@ -52,6 +67,7 @@ final class BurnOnQuitHandler { // MARK: - Burn On Start // In case the burn on quit wasn't successfull + //TODO Rename - application quit handled correctly @UserDefaultsWrapper(key: .burnPerformedSuccessfullyOnQuit, defaultValue: false) private var burnPerformedSuccessfullyOnQuit: Bool diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index feafa950f9..738fa3d2a9 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -222,6 +222,17 @@ extension NSAlert { return alert } + static func burnOnQuitAlert() -> NSAlert { + let alert = NSAlert() + alert.messageText = "Clear data and \nclose all tabs before closing?" + alert.informativeText = "Cookies and site data for all sites will be cleared, unless the site is Fireproof." + alert.alertStyle = .warning + alert.icon = .burnAlert + alert.addButton(withTitle: UserText.clear) + alert.addButton(withTitle: UserText.cancel) + return alert + } + @discardableResult func runModal() async -> NSApplication.ModalResponse { await withCheckedContinuation { continuation in diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 3ab373152e..cd186f3dcf 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1037,9 +1037,19 @@ struct UserText { static let fireproofCheckboxTitle = NSLocalizedString("fireproof.checkbox.title", value: "Ask to Fireproof websites when signing in", comment: "Fireproof settings checkbox title") static let fireproofExplanation = NSLocalizedString("fireproof.explanation", value: "When you Fireproof a site, cookies won't be erased and you'll stay signed in, even after using the Fire Button.", comment: "Fireproofing mechanism explanation") static let manageFireproofSites = NSLocalizedString("fireproof.manage-sites", value: "Manage Fireproof Sites…", comment: "Fireproof settings button caption") - static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically Clear Data", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.") - static let burnDataOnQuit = NSLocalizedString("burn.data.on.quit", value: "Burn data on quit", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") - static let burnDataOnQuitExplanation = NSLocalizedString("burn.data.on.quit.explanation", value: "Clear all browsing data, history, and cookies whenever you close the browser. Cookies for Fireproof Sites won't be erased.", comment: "Explanation of a setting which configures clearing data automatically after quitting the app.") + static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically Clear", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.") + static let burnDataOnQuit = NSLocalizedString("burn.data.on.quit", value: "Clear Data upon Quitting", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") + static let burnDataOnQuitExplanation = NSLocalizedString("burn.data.on.quit.explanation", value: "Data from \"Fireproofed\" sites won't be cleared, keeping your essential logins and preferences intact.", comment: "Explanation of a setting which configures clearing data automatically after quitting the app.") + static let clearDataAfter = NSLocalizedString("clear.data.after", value: "Clear Data After:", comment: "A label for a setting to automatically clear data after some delay") + static func clearDataAfter(for option: ClearDataAfterOption) -> String { + switch option { + case .quittingAppOnly: return NSLocalizedString("quitting.app.only", value: "Quitting App Only", comment: "A label describing an option to clear data automatically only upon quitting the app") + case .quittingApp30MinutesOfInactivity: return NSLocalizedString("quitting.app.30.minutes.of.inactivity", value: "Quitting App, 30 Minutes of Inactivity", comment: "A label describing an option to clear data automatically only upon quitting the app or after 30 minutes of inactivity") + case .quittingApp2HoursOfInactivity: return NSLocalizedString("quitting.app.2.hours.of.inactivity", value: "Quitting App, 2 Hours of Inactivity", comment: "A label describing an option to clear data automatically only upon quitting the app or after 2 hours of inactivity") + case .quittingApp8HoursOfInactivity: return NSLocalizedString("quitting.app.8.hours.of.inactivity", value: "Quitting App, 8 Hours of Inactivity", comment: "A label describing an option to clear data automatically only upon quitting the app or after 8 hours of inactivity") + case .quittingApp1DayOfInactivity: return NSLocalizedString("quitting.app.1.day.of.inactivity", value: "Quitting App, 1 Day of Inactivity", comment: "A label describing an option to clear data automatically only upon quitting the app or after 1 day of inactivity") + } + } static func disableBurnOnQuitToEnableSessionRestore() -> String { let localized = NSLocalizedString("disable.burn.on.quit.to.enable.session.restore", value: "Disable the %@ setting to enable session restore on startup.", diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 11732df24d..6c783005a0 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -55,6 +55,8 @@ public struct UserDefaultsWrapper { case loginDetectionEnabled = "fireproofing.login-detection-enabled" case burnDataOnQuitEnabled = "preferences.burn-data-on-quit-enabled" + case warnBeforeClearingEnabled = "preferences.warn-before-clearing-enabled" + case clearDataAfter = "preferences.clear-data-after" case gpcEnabled = "preferences.gpc-enabled" case selectedDownloadLocationKey = "preferences.download-location" case lastUsedCustomDownloadLocation = "preferences.custom-last-used-download-location" diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 9ff043921e..c5c3789d1d 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -5485,7 +5485,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Automatically Clear Data" + "value" : "Automatically Clear" } } } @@ -9766,7 +9766,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Burn data on quit" + "value" : "Clear Data upon Quitting" } } } @@ -9778,7 +9778,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear all browsing data, history, and cookies whenever you close the browser. Cookies for Fireproof Sites won't be erased." + "value" : "Data from \"Fireproofed\" sites won't be cleared, keeping your essential logins and preferences intact." } } } @@ -10609,6 +10609,18 @@ } } }, + "clear.data.after" : { + "comment" : "A label for a setting to automatically clear data after some delay", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Clear Data After:" + } + } + } + }, "close.other.tabs" : { "comment" : "Menu item", "extractionState" : "extracted_with_value", @@ -44822,6 +44834,66 @@ } } }, + "quitting.app.1.day.of.inactivity" : { + "comment" : "A label describing an option to clear data automatically only upon quitting the app or after 1 day of inactivity", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Quitting App, 1 Day of Inactivity" + } + } + } + }, + "quitting.app.2.hours.of.inactivity" : { + "comment" : "A label describing an option to clear data automatically only upon quitting the app or after 2 hours of inactivity", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Quitting App, 2 Hours of Inactivity" + } + } + } + }, + "quitting.app.8.hours.of.inactivity" : { + "comment" : "A label describing an option to clear data automatically only upon quitting the app or after 8 hours of inactivity", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Quitting App, 8 Hours of Inactivity" + } + } + } + }, + "quitting.app.30.minutes.of.inactivity" : { + "comment" : "A label describing an option to clear data automatically only upon quitting the app or after 30 minutes of inactivity", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Quitting App, 30 Minutes of Inactivity" + } + } + } + }, + "quitting.app.only" : { + "comment" : "A label describing an option to clear data automatically only upon quitting the app", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Quitting App Only" + } + } + } + }, "Recently Closed" : { "comment" : "Main Menu History item", "localizations" : { diff --git a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift index 1f96105a1e..e6a8e3977f 100644 --- a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift @@ -40,6 +40,16 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { } } + @Published + var isWarnBeforeClearingEnabled: Bool { + didSet { + persistor.warnBeforeClearingEnabled = isWarnBeforeClearingEnabled + } + } + + @Published + var clearDataAfter: ClearDataAfterOption = .quittingAppOnly + @MainActor func presentManageFireproofSitesDialog() { let fireproofDomainsWindowController = FireproofDomainsViewController.create().wrappedInWindowController() @@ -58,6 +68,8 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { self.persistor = persistor isLoginDetectionEnabled = persistor.loginDetectionEnabled isBurnDataOnQuitEnabled = persistor.burnDataOnQuitEnabled + isWarnBeforeClearingEnabled = persistor.warnBeforeClearingEnabled + clearDataAfter = persistor.clearDataAfter } private var persistor: FireButtonPreferencesPersistor @@ -66,6 +78,8 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { protocol FireButtonPreferencesPersistor { var loginDetectionEnabled: Bool { get set } var burnDataOnQuitEnabled: Bool { get set } + var warnBeforeClearingEnabled: Bool { get set } + var clearDataAfter: ClearDataAfterOption { get set } } struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersistor { @@ -76,6 +90,20 @@ struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersisto @UserDefaultsWrapper(key: .burnDataOnQuitEnabled, defaultValue: false) var burnDataOnQuitEnabled: Bool + @UserDefaultsWrapper(key: .warnBeforeClearingEnabled, defaultValue: false) + var warnBeforeClearingEnabled: Bool + + @UserDefaultsWrapper(key: .clearDataAfter, defaultValue: .quittingAppOnly) + var clearDataAfter: ClearDataAfterOption + +} + +enum ClearDataAfterOption: String, CaseIterable { + case quittingAppOnly + case quittingApp30MinutesOfInactivity + case quittingApp2HoursOfInactivity + case quittingApp8HoursOfInactivity + case quittingApp1DayOfInactivity } extension Notification.Name { diff --git a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift index 1b886b9cf5..7901c93c14 100644 --- a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift @@ -36,6 +36,20 @@ extension Preferences { VStack(alignment: .leading, spacing: 1) { TextMenuItemCaption(UserText.burnDataOnQuitExplanation) } + ToggleMenuItem("Warn before Clearing", + isOn: $model.isWarnBeforeClearingEnabled) + .disabled(!model.isBurnDataOnQuitEnabled) + .padding(.leading, 16) + } + + PreferencePaneSubSection { + Picker(UserText.clearDataAfter, selection: $model.clearDataAfter) { + ForEach(ClearDataAfterOption.allCases, id: \.self) { option in + Text(UserText.clearDataAfter(for: option)).tag(option) + } + } + .fixedSize() + .disabled(!model.isBurnDataOnQuitEnabled) } } From 20efae9e99bc78cc1e81362c0bb34b832e7fa23a Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Wed, 10 Apr 2024 09:21:37 +0200 Subject: [PATCH 09/22] Burn after --- DuckDuckGo/Application/AppDelegate.swift | 8 ++- .../Application/BurnOnQuitHandler.swift | 63 ++++++++++++++----- DuckDuckGo/Common/Localizables/UserText.swift | 1 + .../Utilities/UserDefaultsWrapper.swift | 2 +- DuckDuckGo/Localizable.xcstrings | 12 ++++ .../Model/DataClearingPreferences.swift | 16 ++++- .../View/PreferencesDataClearingView.swift | 2 +- 7 files changed, 83 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 8e1f260661..13f94cd718 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -309,7 +309,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #endif - burnOnQuitHandler = BurnOnQuitHandler(preferences: .shared, fireCoordinator: FireCoordinator()) + burnOnQuitHandler = BurnOnQuitHandler(preferences: .shared, fireViewModel: FireCoordinator.fireViewModel) burnOnQuitHandler.burnOnStartIfNeeded() burnOnQuitHandler.resetTheFlag() } @@ -335,6 +335,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager.toggleProtectionsCounter.sendEventsIfNeeded() updateSubscriptionStatus() + + burnOnQuitHandler.applicationDidBecomeActive() + } + + func applicationWillResignActive(_ notification: Notification) { + burnOnQuitHandler.applicationWillResignActive() } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/BurnOnQuitHandler.swift index 216a6f8521..3abf22bd99 100644 --- a/DuckDuckGo/Application/BurnOnQuitHandler.swift +++ b/DuckDuckGo/Application/BurnOnQuitHandler.swift @@ -21,14 +21,13 @@ import Combine final class BurnOnQuitHandler { - init(preferences: DataClearingPreferences, - fireCoordinator: FireCoordinator ) { + init(preferences: DataClearingPreferences, fireViewModel: FireViewModel) { self.preferences = preferences - self.fireCoordinator = fireCoordinator + self.fireViewModel = fireViewModel } private let preferences: DataClearingPreferences - private let fireCoordinator: FireCoordinator + private let fireViewModel: FireViewModel // MARK: - Burn On Quit @@ -36,7 +35,7 @@ final class BurnOnQuitHandler { return preferences.isBurnDataOnQuitEnabled } - var shouldWarnOnBurn: Bool { + private var shouldWarnOnBurn: Bool { return preferences.isWarnBeforeClearingEnabled } @@ -51,39 +50,69 @@ final class BurnOnQuitHandler { let alert = NSAlert.burnOnQuitAlert() let response = alert.runModal() guard response == .alertFirstButtonReturn else { - burnPerformedSuccessfullyOnQuit = true + appTerminationHandledCorrectly = true onBurnOnQuitCompleted?() return } } - // TODO: Refactor from static - FireCoordinator.fireViewModel.fire.burnAll { [weak self] in - self?.burnPerformedSuccessfullyOnQuit = true + fireViewModel.fire.burnAll { [weak self] in + self?.appTerminationHandledCorrectly = true self?.onBurnOnQuitCompleted?() } } // MARK: - Burn On Start - // In case the burn on quit wasn't successfull + // In case burning on quit wasn't successful (force quit, crash, etc.) - //TODO Rename - application quit handled correctly - @UserDefaultsWrapper(key: .burnPerformedSuccessfullyOnQuit, defaultValue: false) - private var burnPerformedSuccessfullyOnQuit: Bool + @UserDefaultsWrapper(key: .appTerminationHandledCorrectly, defaultValue: false) + private var appTerminationHandledCorrectly: Bool - var shouldBurnOnStart: Bool { - return !burnPerformedSuccessfullyOnQuit + private var shouldBurnOnStart: Bool { + return shouldBurnOnQuit && !appTerminationHandledCorrectly } func resetTheFlag() { - burnPerformedSuccessfullyOnQuit = false + appTerminationHandledCorrectly = false } @MainActor func burnOnStartIfNeeded() { guard preferences.isBurnDataOnQuitEnabled, shouldBurnOnStart else { return } - FireCoordinator.fireViewModel.fire.burnAll() + fireViewModel.fire.burnAll() + } + + // MARK: - Burn After + + private var inactivityTimer: Timer? + + var shouldBurnAfterDelay: Bool { + preferences.isBurnDataOnQuitEnabled && preferences.clearDataAfter.timeInterval != nil + } + + var burnTimeInterval: Double? { + preferences.clearDataAfter.timeInterval + } + + func applicationDidBecomeActive() { + // Cancel the burn timer if the app becomes active + inactivityTimer?.invalidate() + inactivityTimer = nil + } + + func applicationWillResignActive() { + guard shouldBurnAfterDelay, let interval = burnTimeInterval else { return } + + inactivityTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in + self?.performBurnAfterDelay() + } + } + + private func performBurnAfterDelay() { + DispatchQueue.main.async { [weak self] in + self?.fireViewModel.fire.burnAll() + } } } diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index cd186f3dcf..df5af6f80f 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1040,6 +1040,7 @@ struct UserText { static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically Clear", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.") static let burnDataOnQuit = NSLocalizedString("burn.data.on.quit", value: "Clear Data upon Quitting", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") static let burnDataOnQuitExplanation = NSLocalizedString("burn.data.on.quit.explanation", value: "Data from \"Fireproofed\" sites won't be cleared, keeping your essential logins and preferences intact.", comment: "Explanation of a setting which configures clearing data automatically after quitting the app.") + static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn before Quit", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.") static let clearDataAfter = NSLocalizedString("clear.data.after", value: "Clear Data After:", comment: "A label for a setting to automatically clear data after some delay") static func clearDataAfter(for option: ClearDataAfterOption) -> String { switch option { diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 6c783005a0..0ac06fbe29 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -80,7 +80,7 @@ public struct UserDefaultsWrapper { case lastCrashReportCheckDate = "last.crash.report.check.date" case fireInfoPresentedOnce = "fire.info.presented.once" - case burnPerformedSuccessfullyOnQuit = "burn.performed.successfully.on.quit" + case appTerminationHandledCorrectly = "app.termination.handled.correctly" case restorePreviousSession = "preferences.startup.restore-previous-session" case launchToCustomHomePage = "preferences.startup.launch-to-custom-home-page" diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index c5c3789d1d..0828e3b7f4 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -50833,6 +50833,18 @@ } } }, + "warn.before.quit" : { + "comment" : "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Warn before Quit" + } + } + } + }, "We couldn‘t find any bookmarks." : { "comment" : "Data import error message: Bookmarks weren‘t found.", "localizations" : { diff --git a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift index e6a8e3977f..eb5f428bb7 100644 --- a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift @@ -48,7 +48,11 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { } @Published - var clearDataAfter: ClearDataAfterOption = .quittingAppOnly + var clearDataAfter: ClearDataAfterOption = .quittingAppOnly { + didSet { + persistor.clearDataAfter = clearDataAfter + } + } @MainActor func presentManageFireproofSitesDialog() { @@ -104,6 +108,16 @@ enum ClearDataAfterOption: String, CaseIterable { case quittingApp2HoursOfInactivity case quittingApp8HoursOfInactivity case quittingApp1DayOfInactivity + + var timeInterval: TimeInterval? { + switch self { + case .quittingAppOnly: return nil + case .quittingApp30MinutesOfInactivity: return 30 * 60 + case .quittingApp2HoursOfInactivity: return 2 * 60 * 60 + case .quittingApp8HoursOfInactivity: return 8 * 60 * 60 + case .quittingApp1DayOfInactivity: return 24 * 60 * 60 + } + } } extension Notification.Name { diff --git a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift index 7901c93c14..c2f58f597a 100644 --- a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift @@ -36,7 +36,7 @@ extension Preferences { VStack(alignment: .leading, spacing: 1) { TextMenuItemCaption(UserText.burnDataOnQuitExplanation) } - ToggleMenuItem("Warn before Clearing", + ToggleMenuItem(UserText.warnBeforeQuit, isOn: $model.isWarnBeforeClearingEnabled) .disabled(!model.isBurnDataOnQuitEnabled) .padding(.leading, 16) From ce50bc0c749ac328aa2021b588895c9425566e03 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Thu, 11 Apr 2024 10:21:42 +0200 Subject: [PATCH 10/22] Changes based on Ship Review --- DuckDuckGo/Application/AppDelegate.swift | 29 +++--- .../Application/BurnOnQuitHandler.swift | 56 ++++------- .../Common/Extensions/NSAlertExtension.swift | 4 +- DuckDuckGo/Common/Localizables/UserText.swift | 13 +-- DuckDuckGo/Localizable.xcstrings | 96 +++++-------------- .../Model/DataClearingPreferences.swift | 32 +------ .../Model/StartupPreferences.swift | 21 ++-- .../View/PreferencesDataClearingView.swift | 10 -- 8 files changed, 74 insertions(+), 187 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 13f94cd718..a2ed72b343 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -309,9 +309,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #endif - burnOnQuitHandler = BurnOnQuitHandler(preferences: .shared, fireViewModel: FireCoordinator.fireViewModel) - burnOnQuitHandler.burnOnStartIfNeeded() - burnOnQuitHandler.resetTheFlag() + setUpBurnOnQuitHandler() } func applicationDidBecomeActive(_ notification: Notification) { @@ -335,12 +333,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager.toggleProtectionsCounter.sendEventsIfNeeded() updateSubscriptionStatus() - - burnOnQuitHandler.applicationDidBecomeActive() - } - - func applicationWillResignActive(_ notification: Notification) { - burnOnQuitHandler.applicationWillResignActive() } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -357,13 +349,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } stateRestorationManager?.applicationWillTerminate() - if burnOnQuitHandler.shouldBurnOnQuit { - burnOnQuitHandler.onBurnOnQuitCompleted = { - NSApplication.shared.reply(toApplicationShouldTerminate: true) - } - burnOnQuitHandler.burnOnQuit() - - return .terminateLater + // Handling of "Burn on quit" + if let terminationReply = burnOnQuitHandler.terminationReply() { + return terminationReply } return .terminateNow @@ -582,6 +570,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } } + private func setUpBurnOnQuitHandler() { + burnOnQuitHandler = BurnOnQuitHandler(preferences: .shared, fireViewModel: FireCoordinator.fireViewModel) + burnOnQuitHandler.burnOnStartIfNeeded() + burnOnQuitHandler.resetTheFlag() + burnOnQuitHandler.onBurnOnQuitCompleted = { + NSApplication.shared.reply(toApplicationShouldTerminate: true) + } + } + } func updateSubscriptionStatus() { diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/BurnOnQuitHandler.swift index 3abf22bd99..bded4928a6 100644 --- a/DuckDuckGo/Application/BurnOnQuitHandler.swift +++ b/DuckDuckGo/Application/BurnOnQuitHandler.swift @@ -31,7 +31,7 @@ final class BurnOnQuitHandler { // MARK: - Burn On Quit - var shouldBurnOnQuit: Bool { + private var shouldBurnOnQuit: Bool { return preferences.isBurnDataOnQuitEnabled } @@ -39,20 +39,19 @@ final class BurnOnQuitHandler { return preferences.isWarnBeforeClearingEnabled } - // Completion handler for all quit tasks var onBurnOnQuitCompleted: (() -> Void)? @MainActor - func burnOnQuit() { - guard shouldBurnOnQuit else { return } + private func burnOnQuit() -> NSApplication.TerminateReply { + guard shouldBurnOnQuit else { + return .terminateNow + } if shouldWarnOnBurn { let alert = NSAlert.burnOnQuitAlert() let response = alert.runModal() guard response == .alertFirstButtonReturn else { - appTerminationHandledCorrectly = true - onBurnOnQuitCompleted?() - return + return .terminateCancel } } @@ -60,6 +59,17 @@ final class BurnOnQuitHandler { self?.appTerminationHandledCorrectly = true self?.onBurnOnQuitCompleted?() } + + return .terminateLater + } + + @MainActor + func terminationReply() -> NSApplication.TerminateReply? { + if shouldBurnOnQuit { + return burnOnQuit() + } + + return nil } // MARK: - Burn On Start @@ -83,36 +93,4 @@ final class BurnOnQuitHandler { fireViewModel.fire.burnAll() } - // MARK: - Burn After - - private var inactivityTimer: Timer? - - var shouldBurnAfterDelay: Bool { - preferences.isBurnDataOnQuitEnabled && preferences.clearDataAfter.timeInterval != nil - } - - var burnTimeInterval: Double? { - preferences.clearDataAfter.timeInterval - } - - func applicationDidBecomeActive() { - // Cancel the burn timer if the app becomes active - inactivityTimer?.invalidate() - inactivityTimer = nil - } - - func applicationWillResignActive() { - guard shouldBurnAfterDelay, let interval = burnTimeInterval else { return } - - inactivityTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in - self?.performBurnAfterDelay() - } - } - - private func performBurnAfterDelay() { - DispatchQueue.main.async { [weak self] in - self?.fireViewModel.fire.burnAll() - } - } - } diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 738fa3d2a9..8a6af367b2 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -224,8 +224,8 @@ extension NSAlert { static func burnOnQuitAlert() -> NSAlert { let alert = NSAlert() - alert.messageText = "Clear data and \nclose all tabs before closing?" - alert.informativeText = "Cookies and site data for all sites will be cleared, unless the site is Fireproof." + alert.messageText = UserText.warnBeforeQuitDialogHeader + alert.informativeText = UserText.warnBeforeQuitDialogMessage alert.alertStyle = .warning alert.icon = .burnAlert alert.addButton(withTitle: UserText.clear) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index df5af6f80f..6f2475e8fb 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1041,16 +1041,9 @@ struct UserText { static let burnDataOnQuit = NSLocalizedString("burn.data.on.quit", value: "Clear Data upon Quitting", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") static let burnDataOnQuitExplanation = NSLocalizedString("burn.data.on.quit.explanation", value: "Data from \"Fireproofed\" sites won't be cleared, keeping your essential logins and preferences intact.", comment: "Explanation of a setting which configures clearing data automatically after quitting the app.") static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn before Quit", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.") - static let clearDataAfter = NSLocalizedString("clear.data.after", value: "Clear Data After:", comment: "A label for a setting to automatically clear data after some delay") - static func clearDataAfter(for option: ClearDataAfterOption) -> String { - switch option { - case .quittingAppOnly: return NSLocalizedString("quitting.app.only", value: "Quitting App Only", comment: "A label describing an option to clear data automatically only upon quitting the app") - case .quittingApp30MinutesOfInactivity: return NSLocalizedString("quitting.app.30.minutes.of.inactivity", value: "Quitting App, 30 Minutes of Inactivity", comment: "A label describing an option to clear data automatically only upon quitting the app or after 30 minutes of inactivity") - case .quittingApp2HoursOfInactivity: return NSLocalizedString("quitting.app.2.hours.of.inactivity", value: "Quitting App, 2 Hours of Inactivity", comment: "A label describing an option to clear data automatically only upon quitting the app or after 2 hours of inactivity") - case .quittingApp8HoursOfInactivity: return NSLocalizedString("quitting.app.8.hours.of.inactivity", value: "Quitting App, 8 Hours of Inactivity", comment: "A label describing an option to clear data automatically only upon quitting the app or after 8 hours of inactivity") - case .quittingApp1DayOfInactivity: return NSLocalizedString("quitting.app.1.day.of.inactivity", value: "Quitting App, 1 Day of Inactivity", comment: "A label describing an option to clear data automatically only upon quitting the app or after 1 day of inactivity") - } - } + static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Quit the application and \nclear all dara?", comment: "A header of warning before clearing data on the application termination.") + static let warnBeforeQuitDialogMessage = NSLocalizedString("warn.before.quit.dialog.message", value: "Cookies and site data for all sites will be cleared, unless the site is Fireproof.", comment: "A warning before clearing data on the application termination.") + static func disableBurnOnQuitToEnableSessionRestore() -> String { let localized = NSLocalizedString("disable.burn.on.quit.to.enable.session.restore", value: "Disable the %@ setting to enable session restore on startup.", diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 0828e3b7f4..503260d722 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -10609,18 +10609,6 @@ } } }, - "clear.data.after" : { - "comment" : "A label for a setting to automatically clear data after some delay", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Clear Data After:" - } - } - } - }, "close.other.tabs" : { "comment" : "Menu item", "extractionState" : "extracted_with_value", @@ -44834,66 +44822,6 @@ } } }, - "quitting.app.1.day.of.inactivity" : { - "comment" : "A label describing an option to clear data automatically only upon quitting the app or after 1 day of inactivity", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Quitting App, 1 Day of Inactivity" - } - } - } - }, - "quitting.app.2.hours.of.inactivity" : { - "comment" : "A label describing an option to clear data automatically only upon quitting the app or after 2 hours of inactivity", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Quitting App, 2 Hours of Inactivity" - } - } - } - }, - "quitting.app.8.hours.of.inactivity" : { - "comment" : "A label describing an option to clear data automatically only upon quitting the app or after 8 hours of inactivity", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Quitting App, 8 Hours of Inactivity" - } - } - } - }, - "quitting.app.30.minutes.of.inactivity" : { - "comment" : "A label describing an option to clear data automatically only upon quitting the app or after 30 minutes of inactivity", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Quitting App, 30 Minutes of Inactivity" - } - } - } - }, - "quitting.app.only" : { - "comment" : "A label describing an option to clear data automatically only upon quitting the app", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Quitting App Only" - } - } - } - }, "Recently Closed" : { "comment" : "Main Menu History item", "localizations" : { @@ -50845,6 +50773,30 @@ } } }, + "warn.before.quit.dialog.header" : { + "comment" : "A header of warning before clearing data on the application termination.", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit the application and \nclear all dara?" + } + } + } + }, + "warn.before.quit.dialog.message" : { + "comment" : "A warning before clearing data on the application termination.", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Cookies and site data for all sites will be cleared, unless the site is Fireproof." + } + } + } + }, "We couldn‘t find any bookmarks." : { "comment" : "Data import error message: Bookmarks weren‘t found.", "localizations" : { diff --git a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift index eb5f428bb7..3c405140a5 100644 --- a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift @@ -36,7 +36,7 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { persistor.burnDataOnQuitEnabled = isBurnDataOnQuitEnabled NotificationCenter.default.post(name: .burnDataOnQuitDidChange, object: nil, - userInfo: [Self.burnOnQuitNotificationKey: isBurnDataOnQuitEnabled]) + userInfo: nil) } } @@ -47,13 +47,6 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { } } - @Published - var clearDataAfter: ClearDataAfterOption = .quittingAppOnly { - didSet { - persistor.clearDataAfter = clearDataAfter - } - } - @MainActor func presentManageFireproofSitesDialog() { let fireproofDomainsWindowController = FireproofDomainsViewController.create().wrappedInWindowController() @@ -73,7 +66,6 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { isLoginDetectionEnabled = persistor.loginDetectionEnabled isBurnDataOnQuitEnabled = persistor.burnDataOnQuitEnabled isWarnBeforeClearingEnabled = persistor.warnBeforeClearingEnabled - clearDataAfter = persistor.clearDataAfter } private var persistor: FireButtonPreferencesPersistor @@ -83,7 +75,6 @@ protocol FireButtonPreferencesPersistor { var loginDetectionEnabled: Bool { get set } var burnDataOnQuitEnabled: Bool { get set } var warnBeforeClearingEnabled: Bool { get set } - var clearDataAfter: ClearDataAfterOption { get set } } struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersistor { @@ -97,27 +88,6 @@ struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersisto @UserDefaultsWrapper(key: .warnBeforeClearingEnabled, defaultValue: false) var warnBeforeClearingEnabled: Bool - @UserDefaultsWrapper(key: .clearDataAfter, defaultValue: .quittingAppOnly) - var clearDataAfter: ClearDataAfterOption - -} - -enum ClearDataAfterOption: String, CaseIterable { - case quittingAppOnly - case quittingApp30MinutesOfInactivity - case quittingApp2HoursOfInactivity - case quittingApp8HoursOfInactivity - case quittingApp1DayOfInactivity - - var timeInterval: TimeInterval? { - switch self { - case .quittingAppOnly: return nil - case .quittingApp30MinutesOfInactivity: return 30 * 60 - case .quittingApp2HoursOfInactivity: return 2 * 60 * 60 - case .quittingApp8HoursOfInactivity: return 8 * 60 * 60 - case .quittingApp1DayOfInactivity: return 24 * 60 * 60 - } - } } extension Notification.Name { diff --git a/DuckDuckGo/Preferences/Model/StartupPreferences.swift b/DuckDuckGo/Preferences/Model/StartupPreferences.swift index bec557d2da..11ab60e384 100644 --- a/DuckDuckGo/Preferences/Model/StartupPreferences.swift +++ b/DuckDuckGo/Preferences/Model/StartupPreferences.swift @@ -46,18 +46,22 @@ final class StartupPreferences: ObservableObject, PreferencesTabOpening { private let pinningManager: LocalPinningManager private var persistor: StartupPreferencesPersistor private var pinnedViewsNotificationCancellable: AnyCancellable? + private var dataClearingPreferences: DataClearingPreferences private var dataClearingPreferencesNotificationCancellable: AnyCancellable? init(pinningManager: LocalPinningManager = LocalPinningManager.shared, - persistor: StartupPreferencesPersistor = StartupPreferencesUserDefaultsPersistor(appearancePrefs: AppearancePreferences.shared)) { + persistor: StartupPreferencesPersistor = StartupPreferencesUserDefaultsPersistor(appearancePrefs: AppearancePreferences.shared), + dataClearingPreferences: DataClearingPreferences = DataClearingPreferences.shared) { self.pinningManager = pinningManager self.persistor = persistor + self.dataClearingPreferences = dataClearingPreferences restorePreviousSession = persistor.restorePreviousSession launchToCustomHomePage = persistor.launchToCustomHomePage customHomePageURL = persistor.customHomePageURL updateHomeButtonState() listenToPinningManagerNotifications() listenToDataClearingPreferencesNotifications() + checkDataClearingStatus() } @Published var restorePreviousSession: Bool { @@ -131,15 +135,18 @@ final class StartupPreferences: ObservableObject, PreferencesTabOpening { } } + private func checkDataClearingStatus() { + if dataClearingPreferences.isBurnDataOnQuitEnabled { + restorePreviousSession = false + } + } + private func listenToDataClearingPreferencesNotifications() { - dataClearingPreferencesNotificationCancellable = NotificationCenter.default.publisher(for: .burnDataOnQuitDidChange).sink { [weak self] notification in - guard let self = self, - let isBurnDataOnQuitEnabled = notification.userInfo?[DataClearingPreferences.burnOnQuitNotificationKey] as? Bool else { + dataClearingPreferencesNotificationCancellable = NotificationCenter.default.publisher(for: .burnDataOnQuitDidChange).sink { [weak self] _ in + guard let self = self else { return } - if isBurnDataOnQuitEnabled { - self.restorePreviousSession = false - } + self.checkDataClearingStatus() } } diff --git a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift index c2f58f597a..0fb1e94feb 100644 --- a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift @@ -42,16 +42,6 @@ extension Preferences { .padding(.leading, 16) } - PreferencePaneSubSection { - Picker(UserText.clearDataAfter, selection: $model.clearDataAfter) { - ForEach(ClearDataAfterOption.allCases, id: \.self) { option in - Text(UserText.clearDataAfter(for: option)).tag(option) - } - } - .fixedSize() - .disabled(!model.isBurnDataOnQuitEnabled) - } - } // SECTION 2: Fireproof Site From da7202f41dceeb2f2123e16b697f7ebd3f18c175 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Thu, 11 Apr 2024 10:44:59 +0200 Subject: [PATCH 11/22] Copy fixed --- DuckDuckGo/Common/Localizables/UserText.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 6f2475e8fb..b78dc33ada 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1041,7 +1041,7 @@ struct UserText { static let burnDataOnQuit = NSLocalizedString("burn.data.on.quit", value: "Clear Data upon Quitting", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") static let burnDataOnQuitExplanation = NSLocalizedString("burn.data.on.quit.explanation", value: "Data from \"Fireproofed\" sites won't be cleared, keeping your essential logins and preferences intact.", comment: "Explanation of a setting which configures clearing data automatically after quitting the app.") static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn before Quit", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.") - static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Quit the application and \nclear all dara?", comment: "A header of warning before clearing data on the application termination.") + static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Quit the application and \nclear all data?", comment: "A header of warning before clearing data on the application termination.") static let warnBeforeQuitDialogMessage = NSLocalizedString("warn.before.quit.dialog.message", value: "Cookies and site data for all sites will be cleared, unless the site is Fireproof.", comment: "A warning before clearing data on the application termination.") static func disableBurnOnQuitToEnableSessionRestore() -> String { From 857e7edace42b4ba575b199dab340256baaf3845 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Thu, 11 Apr 2024 11:31:03 +0200 Subject: [PATCH 12/22] Refactoring --- DuckDuckGo/Application/AppDelegate.swift | 2 +- .../Application/BurnOnQuitHandler.swift | 69 +++++++------------ DuckDuckGo/Localizable.xcstrings | 2 +- 3 files changed, 28 insertions(+), 45 deletions(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index a2ed72b343..2f4cba4182 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -350,7 +350,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel stateRestorationManager?.applicationWillTerminate() // Handling of "Burn on quit" - if let terminationReply = burnOnQuitHandler.terminationReply() { + if let terminationReply = burnOnQuitHandler.handleAppTermination() { return terminationReply } diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/BurnOnQuitHandler.swift index bded4928a6..7ea4ce1616 100644 --- a/DuckDuckGo/Application/BurnOnQuitHandler.swift +++ b/DuckDuckGo/Application/BurnOnQuitHandler.swift @@ -21,76 +21,59 @@ import Combine final class BurnOnQuitHandler { + private let preferences: DataClearingPreferences + private let fireViewModel: FireViewModel + init(preferences: DataClearingPreferences, fireViewModel: FireViewModel) { self.preferences = preferences self.fireViewModel = fireViewModel } - private let preferences: DataClearingPreferences - private let fireViewModel: FireViewModel + var onBurnOnQuitCompleted: (() -> Void)? - // MARK: - Burn On Quit + @MainActor + func handleAppTermination() -> NSApplication.TerminateReply? { + guard preferences.isBurnDataOnQuitEnabled else { return nil } - private var shouldBurnOnQuit: Bool { - return preferences.isBurnDataOnQuitEnabled - } + if preferences.isWarnBeforeClearingEnabled, !confirmBurnOnQuit() { + return .terminateCancel + } - private var shouldWarnOnBurn: Bool { - return preferences.isWarnBeforeClearingEnabled + performBurnOnQuit() + return .terminateLater } - var onBurnOnQuitCompleted: (() -> Void)? + func resetTheFlag() { + appTerminationHandledCorrectly = false + } - @MainActor - private func burnOnQuit() -> NSApplication.TerminateReply { - guard shouldBurnOnQuit else { - return .terminateNow - } + // MARK: - Private - if shouldWarnOnBurn { - let alert = NSAlert.burnOnQuitAlert() - let response = alert.runModal() - guard response == .alertFirstButtonReturn else { - return .terminateCancel - } - } + private func confirmBurnOnQuit() -> Bool { + let alert = NSAlert.burnOnQuitAlert() + let response = alert.runModal() + return response == .alertFirstButtonReturn + } + @MainActor + private func performBurnOnQuit() { fireViewModel.fire.burnAll { [weak self] in self?.appTerminationHandledCorrectly = true self?.onBurnOnQuitCompleted?() } - - return .terminateLater - } - - @MainActor - func terminationReply() -> NSApplication.TerminateReply? { - if shouldBurnOnQuit { - return burnOnQuit() - } - - return nil } // MARK: - Burn On Start - // In case burning on quit wasn't successful (force quit, crash, etc.) + // Burning on quit wasn't successful @UserDefaultsWrapper(key: .appTerminationHandledCorrectly, defaultValue: false) private var appTerminationHandledCorrectly: Bool - private var shouldBurnOnStart: Bool { - return shouldBurnOnQuit && !appTerminationHandledCorrectly - } - - func resetTheFlag() { - appTerminationHandledCorrectly = false - } - @MainActor func burnOnStartIfNeeded() { - guard preferences.isBurnDataOnQuitEnabled, shouldBurnOnStart else { return } + let shouldBurnOnStart = preferences.isBurnDataOnQuitEnabled && !appTerminationHandledCorrectly + guard shouldBurnOnStart else { return } fireViewModel.fire.burnAll() } - } diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 503260d722..2133dd374f 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -50780,7 +50780,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Quit the application and \nclear all dara?" + "value" : "Quit the application and \nclear all data?" } } } From 1d8bfff9a90bde7d956e2ab041d599098e0b245c Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Thu, 11 Apr 2024 12:08:48 +0200 Subject: [PATCH 13/22] Unit Tests --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++ .../Application/BurnOnQuitHandler.swift | 7 +- .../AppDelegate/BurnOnQuitHandlerTests.swift | 77 +++++++++++++++++++ .../DataClearingPreferencesTests.swift | 4 + 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 UnitTests/AppDelegate/BurnOnQuitHandlerTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 63ae013877..51d67f5cb9 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -72,6 +72,8 @@ 1D43EB38292B636E0065E5D6 /* BWCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D43EB37292B636E0065E5D6 /* BWCommand.swift */; }; 1D43EB3A292B63B00065E5D6 /* BWRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D43EB39292B63B00065E5D6 /* BWRequest.swift */; }; 1D43EB3C292B664A0065E5D6 /* BWMessageIdGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D43EB3B292B664A0065E5D6 /* BWMessageIdGenerator.swift */; }; + 1D5FADA22BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D5FADA12BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift */; }; + 1D5FADA32BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D5FADA12BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift */; }; 1D6216B229069BBF00386B2C /* BWKeyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6216B129069BBF00386B2C /* BWKeyStorage.swift */; }; 1D69C553291302F200B75945 /* BWVault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D69C552291302F200B75945 /* BWVault.swift */; }; 1D6A492029CF7A490011DF74 /* NSPopoverExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A491F29CF7A490011DF74 /* NSPopoverExtension.swift */; }; @@ -3496,6 +3498,7 @@ 1D43EB37292B636E0065E5D6 /* BWCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWCommand.swift; sourceTree = ""; }; 1D43EB39292B63B00065E5D6 /* BWRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWRequest.swift; sourceTree = ""; }; 1D43EB3B292B664A0065E5D6 /* BWMessageIdGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWMessageIdGenerator.swift; sourceTree = ""; }; + 1D5FADA12BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BurnOnQuitHandlerTests.swift; sourceTree = ""; }; 1D6216B129069BBF00386B2C /* BWKeyStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWKeyStorage.swift; sourceTree = ""; }; 1D69C552291302F200B75945 /* BWVault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWVault.swift; sourceTree = ""; }; 1D6A491F29CF7A490011DF74 /* NSPopoverExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPopoverExtension.swift; sourceTree = ""; }; @@ -6801,6 +6804,7 @@ isa = PBXGroup; children = ( 85F1B0C825EF9759004792B6 /* URLEventHandlerTests.swift */, + 1D5FADA12BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift */, 0289041E2A7B23CE0028369C /* AppConfigurationURLProviderTests.swift */, ); path = AppDelegate; @@ -10957,6 +10961,7 @@ 3706FE46293F661700E42796 /* EncryptedValueTransformerTests.swift in Sources */, 9F3910632B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */, 3706FE47293F661700E42796 /* URLExtensionTests.swift in Sources */, + 1D5FADA32BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift in Sources */, 1DB9617B29F1D06D00CF5568 /* InternalUserDeciderMock.swift in Sources */, 317295D52AF058D3002C3206 /* MockWaitlistFeatureSetupHandler.swift in Sources */, 3706FE48293F661700E42796 /* PixelTests.swift in Sources */, @@ -13025,6 +13030,7 @@ 85F487B5276A8F2E003CE668 /* OnboardingTests.swift in Sources */, B626A7642992506A00053070 /* SerpHeadersNavigationResponderTests.swift in Sources */, AA652CCE25DD9071009059CC /* BookmarkListTests.swift in Sources */, + 1D5FADA22BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift in Sources */, 859E7D6D274548F2009C2B69 /* BookmarksExporterTests.swift in Sources */, B6A5A2A825BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift in Sources */, B6AE39F129373AF200C37AA4 /* EmptyAttributionRulesProver.swift in Sources */, diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/BurnOnQuitHandler.swift index 7ea4ce1616..f3c5416b04 100644 --- a/DuckDuckGo/Application/BurnOnQuitHandler.swift +++ b/DuckDuckGo/Application/BurnOnQuitHandler.swift @@ -70,10 +70,13 @@ final class BurnOnQuitHandler { private var appTerminationHandledCorrectly: Bool @MainActor - func burnOnStartIfNeeded() { + @discardableResult + func burnOnStartIfNeeded() -> Bool { let shouldBurnOnStart = preferences.isBurnDataOnQuitEnabled && !appTerminationHandledCorrectly - guard shouldBurnOnStart else { return } + guard shouldBurnOnStart else { return false } fireViewModel.fire.burnAll() + return true } + } diff --git a/UnitTests/AppDelegate/BurnOnQuitHandlerTests.swift b/UnitTests/AppDelegate/BurnOnQuitHandlerTests.swift new file mode 100644 index 0000000000..9a06bd3f0d --- /dev/null +++ b/UnitTests/AppDelegate/BurnOnQuitHandlerTests.swift @@ -0,0 +1,77 @@ +// +// BurnOnQuitHandlerTests.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import XCTest + +@testable import DuckDuckGo_Privacy_Browser + +@MainActor +class BurnOnQuitHandlerTests: XCTestCase { + + var handler: BurnOnQuitHandler! + var preferences: DataClearingPreferences! + var fireViewModel: FireViewModel! + + override func setUp() { + super.setUp() + let persistor = MockFireButtonPreferencesPersistor() + preferences = DataClearingPreferences(persistor: persistor) + fireViewModel = FireViewModel(fire: Fire(tld: ContentBlocking.shared.tld)) + handler = BurnOnQuitHandler(preferences: preferences, fireViewModel: fireViewModel) + } + + override func tearDown() { + handler = nil + preferences = nil + fireViewModel = nil + super.tearDown() + } + + func testWhenBurningEnabledAndNoWarningRequiredThenTerminateLaterIsReturned() { + preferences.isBurnDataOnQuitEnabled = true + preferences.isWarnBeforeClearingEnabled = false + + let response = handler.handleAppTermination() + + XCTAssertEqual(response, .terminateLater) + } + + func testWhenBurningDisabledThenNoTerminationResponse() { + preferences.isBurnDataOnQuitEnabled = false + + let response = handler.handleAppTermination() + + XCTAssertNil(response) + } + + func testWhenBurningEnabledAndFlagFalseThenBurnOnStartTriggered() { + preferences.isBurnDataOnQuitEnabled = true + handler.resetTheFlag() + + XCTAssertTrue(handler.burnOnStartIfNeeded()) + } + + func testWhenBurningDisabledThenBurnOnStartNotTriggered() { + preferences.isBurnDataOnQuitEnabled = false + handler.resetTheFlag() + + XCTAssertFalse(handler.burnOnStartIfNeeded()) + } + +} diff --git a/UnitTests/Preferences/DataClearingPreferencesTests.swift b/UnitTests/Preferences/DataClearingPreferencesTests.swift index 5563fb6e90..3fb71b91d6 100644 --- a/UnitTests/Preferences/DataClearingPreferencesTests.swift +++ b/UnitTests/Preferences/DataClearingPreferencesTests.swift @@ -20,7 +20,11 @@ import XCTest @testable import DuckDuckGo_Privacy_Browser class MockFireButtonPreferencesPersistor: FireButtonPreferencesPersistor { + + var burnDataOnQuitEnabled: Bool = false + var warnBeforeClearingEnabled: Bool = false var loginDetectionEnabled: Bool = false + } class DataClearingPreferencesTests: XCTestCase { From 4aa5d52e359544efe373637a5c8e192c6a785402 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Fri, 12 Apr 2024 09:26:29 +0200 Subject: [PATCH 14/22] Copy changed --- DuckDuckGo.xcodeproj/project.pbxproj | 28 +++++----- DuckDuckGo/Application/AppDelegate.swift | 16 +++--- ...itHandler.swift => AutoClearHandler.swift} | 22 ++++---- .../Common/Extensions/NSAlertExtension.swift | 4 +- DuckDuckGo/Common/Localizables/UserText.swift | 19 +++---- .../Utilities/UserDefaultsWrapper.swift | 2 +- DuckDuckGo/Localizable.xcstrings | 56 ++++++++----------- .../Model/DataClearingPreferences.swift | 17 +++--- .../Model/StartupPreferences.swift | 4 +- .../View/PreferencesDataClearingView.swift | 9 +-- .../View/PreferencesGeneralView.swift | 6 +- ...ests.swift => AutoClearHandlerTests.swift} | 16 +++--- .../DataClearingPreferencesTests.swift | 2 +- 13 files changed, 92 insertions(+), 109 deletions(-) rename DuckDuckGo/Application/{BurnOnQuitHandler.swift => AutoClearHandler.swift} (76%) rename UnitTests/AppDelegate/{BurnOnQuitHandlerTests.swift => AutoClearHandlerTests.swift} (82%) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a5983ad947..eab0e54705 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -28,9 +28,9 @@ 1D01A3DA2B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3D72B88DF8B00FE8150 /* PreferencesSyncView.swift */; }; 1D02633628D8A9A9005CBB41 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; settings = {COMPILER_FLAGS = "-Wno-deprecated -Wno-strict-prototypes"; }; }; 1D074B272909A433006E4AC3 /* PasswordManagerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D074B262909A433006E4AC3 /* PasswordManagerCoordinator.swift */; }; - 1D0E4F562BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */; }; - 1D0E4F572BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */; }; - 1D0E4F582BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */; }; + 1D0E4F562BB419ED00670CFE /* AutoClearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* AutoClearHandler.swift */; }; + 1D0E4F572BB419ED00670CFE /* AutoClearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* AutoClearHandler.swift */; }; + 1D0E4F582BB419ED00670CFE /* AutoClearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0E4F552BB419ED00670CFE /* AutoClearHandler.swift */; }; 1D12F2E2298BC660009A65FD /* InternalUserDeciderStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D12F2E1298BC660009A65FD /* InternalUserDeciderStoreMock.swift */; }; 1D1A33492A6FEB170080ACED /* BurnerMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1A33482A6FEB170080ACED /* BurnerMode.swift */; }; 1D1A334A2A6FEB170080ACED /* BurnerMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1A33482A6FEB170080ACED /* BurnerMode.swift */; }; @@ -72,8 +72,8 @@ 1D43EB38292B636E0065E5D6 /* BWCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D43EB37292B636E0065E5D6 /* BWCommand.swift */; }; 1D43EB3A292B63B00065E5D6 /* BWRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D43EB39292B63B00065E5D6 /* BWRequest.swift */; }; 1D43EB3C292B664A0065E5D6 /* BWMessageIdGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D43EB3B292B664A0065E5D6 /* BWMessageIdGenerator.swift */; }; - 1D5FADA22BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D5FADA12BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift */; }; - 1D5FADA32BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D5FADA12BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift */; }; + 1D5FADA22BC7E734005D6EB1 /* AutoClearHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D5FADA12BC7E734005D6EB1 /* AutoClearHandlerTests.swift */; }; + 1D5FADA32BC7E734005D6EB1 /* AutoClearHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D5FADA12BC7E734005D6EB1 /* AutoClearHandlerTests.swift */; }; 1D6216B229069BBF00386B2C /* BWKeyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6216B129069BBF00386B2C /* BWKeyStorage.swift */; }; 1D69C553291302F200B75945 /* BWVault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D69C552291302F200B75945 /* BWVault.swift */; }; 1D6A492029CF7A490011DF74 /* NSPopoverExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A491F29CF7A490011DF74 /* NSPopoverExtension.swift */; }; @@ -3573,7 +3573,7 @@ 1D02633428D8A9A9005CBB41 /* BWEncryption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BWEncryption.h; sourceTree = ""; }; 1D02633528D8A9A9005CBB41 /* BWEncryption.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BWEncryption.m; sourceTree = ""; }; 1D074B262909A433006E4AC3 /* PasswordManagerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordManagerCoordinator.swift; sourceTree = ""; }; - 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurnOnQuitHandler.swift; sourceTree = ""; }; + 1D0E4F552BB419ED00670CFE /* AutoClearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoClearHandler.swift; sourceTree = ""; }; 1D12F2E1298BC660009A65FD /* InternalUserDeciderStoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalUserDeciderStoreMock.swift; sourceTree = ""; }; 1D1A33482A6FEB170080ACED /* BurnerMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurnerMode.swift; sourceTree = ""; }; 1D1C36E229FAE8DA001FA40C /* FaviconManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconManagerTests.swift; sourceTree = ""; }; @@ -3597,7 +3597,7 @@ 1D43EB37292B636E0065E5D6 /* BWCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWCommand.swift; sourceTree = ""; }; 1D43EB39292B63B00065E5D6 /* BWRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWRequest.swift; sourceTree = ""; }; 1D43EB3B292B664A0065E5D6 /* BWMessageIdGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWMessageIdGenerator.swift; sourceTree = ""; }; - 1D5FADA12BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BurnOnQuitHandlerTests.swift; sourceTree = ""; }; + 1D5FADA12BC7E734005D6EB1 /* AutoClearHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoClearHandlerTests.swift; sourceTree = ""; }; 1D6216B129069BBF00386B2C /* BWKeyStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWKeyStorage.swift; sourceTree = ""; }; 1D69C552291302F200B75945 /* BWVault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BWVault.swift; sourceTree = ""; }; 1D6A491F29CF7A490011DF74 /* NSPopoverExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPopoverExtension.swift; sourceTree = ""; }; @@ -6975,7 +6975,7 @@ isa = PBXGroup; children = ( 85F1B0C825EF9759004792B6 /* URLEventHandlerTests.swift */, - 1D5FADA12BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift */, + 1D5FADA12BC7E734005D6EB1 /* AutoClearHandlerTests.swift */, 0289041E2A7B23CE0028369C /* AppConfigurationURLProviderTests.swift */, ); path = AppDelegate; @@ -7179,7 +7179,7 @@ 858A798226A8B75F00A75A42 /* CopyHandler.swift */, 1D36E65A298ACD2900AA485D /* AppIconChanger.swift */, CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */, - 1D0E4F552BB419ED00670CFE /* BurnOnQuitHandler.swift */, + 1D0E4F552BB419ED00670CFE /* AutoClearHandler.swift */, ); path = Application; sourceTree = ""; @@ -10949,7 +10949,7 @@ 3706FC52293F65D500E42796 /* MouseOverAnimationButton.swift in Sources */, B60293E72BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */, 3706FC53293F65D500E42796 /* TabBarScrollView.swift in Sources */, - 1D0E4F572BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */, + 1D0E4F572BB419ED00670CFE /* AutoClearHandler.swift in Sources */, B6104E9C2BA9C173008636B2 /* DownloadResumeData.swift in Sources */, 3706FC54293F65D500E42796 /* BookmarkListTreeControllerDataSource.swift in Sources */, 3706FC55293F65D500E42796 /* AddressBarViewController.swift in Sources */, @@ -11223,7 +11223,7 @@ 3706FE46293F661700E42796 /* EncryptedValueTransformerTests.swift in Sources */, 9F3910632B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */, 3706FE47293F661700E42796 /* URLExtensionTests.swift in Sources */, - 1D5FADA32BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift in Sources */, + 1D5FADA32BC7E734005D6EB1 /* AutoClearHandlerTests.swift in Sources */, 1DB9617B29F1D06D00CF5568 /* InternalUserDeciderMock.swift in Sources */, 317295D52AF058D3002C3206 /* MockWaitlistFeatureSetupHandler.swift in Sources */, 3706FE48293F661700E42796 /* PixelTests.swift in Sources */, @@ -12043,7 +12043,7 @@ 4B957AF12AC7AE700062CA31 /* AppStateRestorationManager.swift in Sources */, 4B957AF22AC7AE700062CA31 /* DailyPixel.swift in Sources */, 9FDA6C232B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, - 1D0E4F582BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */, + 1D0E4F582BB419ED00670CFE /* AutoClearHandler.swift in Sources */, 4B957AF32AC7AE700062CA31 /* NavigationHotkeyHandler.swift in Sources */, B6CC266E2BAD9CD800F53F8D /* FileProgressPresenter.swift in Sources */, 4B957AF42AC7AE700062CA31 /* ClickToLoadUserScript.swift in Sources */, @@ -12471,7 +12471,7 @@ B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */, AA5FA697275F90C400DCE9C9 /* FaviconImageCache.swift in Sources */, 1430DFF524D0580F00B8978C /* TabBarViewController.swift in Sources */, - 1D0E4F562BB419ED00670CFE /* BurnOnQuitHandler.swift in Sources */, + 1D0E4F562BB419ED00670CFE /* AutoClearHandler.swift in Sources */, B62B483E2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, 4B92929B26670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift in Sources */, 56D145EB29E6C99B00E3488A /* DataImportStatusProviding.swift in Sources */, @@ -13334,7 +13334,7 @@ 85F487B5276A8F2E003CE668 /* OnboardingTests.swift in Sources */, B626A7642992506A00053070 /* SerpHeadersNavigationResponderTests.swift in Sources */, AA652CCE25DD9071009059CC /* BookmarkListTests.swift in Sources */, - 1D5FADA22BC7E734005D6EB1 /* BurnOnQuitHandlerTests.swift in Sources */, + 1D5FADA22BC7E734005D6EB1 /* AutoClearHandlerTests.swift in Sources */, 859E7D6D274548F2009C2B69 /* BookmarksExporterTests.swift in Sources */, B6A5A2A825BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift in Sources */, B6AE39F129373AF200C37AA4 /* EmptyAttributionRulesProver.swift in Sources */, diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 7405cffd25..eada31b66d 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -70,7 +70,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let internalUserDecider: InternalUserDecider let featureFlagger: FeatureFlagger private var appIconChanger: AppIconChanger! - private var burnOnQuitHandler: BurnOnQuitHandler! + private var autoClearHandler: AutoClearHandler! private(set) var syncDataProviders: SyncDataProviders! private(set) var syncService: DDGSyncing? @@ -313,7 +313,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #endif - setUpBurnOnQuitHandler() + setUpAutoClearHandler() } func applicationDidBecomeActive(_ notification: Notification) { @@ -352,7 +352,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { stateRestorationManager?.applicationWillTerminate() // Handling of "Burn on quit" - if let terminationReply = burnOnQuitHandler.handleAppTermination() { + if let terminationReply = autoClearHandler.handleAppTermination() { return terminationReply } @@ -556,11 +556,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } } - private func setUpBurnOnQuitHandler() { - burnOnQuitHandler = BurnOnQuitHandler(preferences: .shared, fireViewModel: FireCoordinator.fireViewModel) - burnOnQuitHandler.burnOnStartIfNeeded() - burnOnQuitHandler.resetTheFlag() - burnOnQuitHandler.onBurnOnQuitCompleted = { + private func setUpAutoClearHandler() { + autoClearHandler = AutoClearHandler(preferences: .shared, fireViewModel: FireCoordinator.fireViewModel) + autoClearHandler.burnOnStartIfNeeded() + autoClearHandler.resetTheFlag() + autoClearHandler.onAutoClearCompleted = { NSApplication.shared.reply(toApplicationShouldTerminate: true) } } diff --git a/DuckDuckGo/Application/BurnOnQuitHandler.swift b/DuckDuckGo/Application/AutoClearHandler.swift similarity index 76% rename from DuckDuckGo/Application/BurnOnQuitHandler.swift rename to DuckDuckGo/Application/AutoClearHandler.swift index f3c5416b04..1ed2bf4837 100644 --- a/DuckDuckGo/Application/BurnOnQuitHandler.swift +++ b/DuckDuckGo/Application/AutoClearHandler.swift @@ -1,5 +1,5 @@ // -// BurnOnQuitHandler.swift +// AutoClearHandler.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // @@ -19,7 +19,7 @@ import Foundation import Combine -final class BurnOnQuitHandler { +final class AutoClearHandler { private let preferences: DataClearingPreferences private let fireViewModel: FireViewModel @@ -29,17 +29,17 @@ final class BurnOnQuitHandler { self.fireViewModel = fireViewModel } - var onBurnOnQuitCompleted: (() -> Void)? + var onAutoClearCompleted: (() -> Void)? @MainActor func handleAppTermination() -> NSApplication.TerminateReply? { - guard preferences.isBurnDataOnQuitEnabled else { return nil } + guard preferences.isAutoClearEnabled else { return nil } - if preferences.isWarnBeforeClearingEnabled, !confirmBurnOnQuit() { + if preferences.isWarnBeforeClearingEnabled, !confirmAutoClear() { return .terminateCancel } - performBurnOnQuit() + performAutoClear() return .terminateLater } @@ -49,17 +49,17 @@ final class BurnOnQuitHandler { // MARK: - Private - private func confirmBurnOnQuit() -> Bool { - let alert = NSAlert.burnOnQuitAlert() + private func confirmAutoClear() -> Bool { + let alert = NSAlert.autoClearAlert() let response = alert.runModal() return response == .alertFirstButtonReturn } @MainActor - private func performBurnOnQuit() { + private func performAutoClear() { fireViewModel.fire.burnAll { [weak self] in self?.appTerminationHandledCorrectly = true - self?.onBurnOnQuitCompleted?() + self?.onAutoClearCompleted?() } } @@ -72,7 +72,7 @@ final class BurnOnQuitHandler { @MainActor @discardableResult func burnOnStartIfNeeded() -> Bool { - let shouldBurnOnStart = preferences.isBurnDataOnQuitEnabled && !appTerminationHandledCorrectly + let shouldBurnOnStart = preferences.isAutoClearEnabled && !appTerminationHandledCorrectly guard shouldBurnOnStart else { return false } fireViewModel.fire.burnAll() diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 0f7246d89d..4576a98515 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -215,10 +215,10 @@ extension NSAlert { return alert } - static func burnOnQuitAlert() -> NSAlert { + static func autoClearAlert() -> NSAlert { let alert = NSAlert() alert.messageText = UserText.warnBeforeQuitDialogHeader - alert.informativeText = UserText.warnBeforeQuitDialogMessage + //TODO checkbox alert.alertStyle = .warning alert.icon = .burnAlert alert.addButton(withTitle: UserText.clear) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 7e5714381d..787ab1d4cc 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1045,18 +1045,17 @@ struct UserText { static let fireproofCheckboxTitle = NSLocalizedString("fireproof.checkbox.title", value: "Ask to Fireproof websites when signing in", comment: "Fireproof settings checkbox title") static let fireproofExplanation = NSLocalizedString("fireproof.explanation", value: "When you Fireproof a site, cookies won't be erased and you'll stay signed in, even after using the Fire Button.", comment: "Fireproofing mechanism explanation") static let manageFireproofSites = NSLocalizedString("fireproof.manage-sites", value: "Manage Fireproof Sites…", comment: "Fireproof settings button caption") - static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically Clear", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.") - static let burnDataOnQuit = NSLocalizedString("burn.data.on.quit", value: "Clear Data upon Quitting", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") - static let burnDataOnQuitExplanation = NSLocalizedString("burn.data.on.quit.explanation", value: "Data from \"Fireproofed\" sites won't be cleared, keeping your essential logins and preferences intact.", comment: "Explanation of a setting which configures clearing data automatically after quitting the app.") - static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn before Quit", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.") - static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Quit the application and \nclear all data?", comment: "A header of warning before clearing data on the application termination.") - static let warnBeforeQuitDialogMessage = NSLocalizedString("warn.before.quit.dialog.message", value: "Cookies and site data for all sites will be cleared, unless the site is Fireproof.", comment: "A warning before clearing data on the application termination.") - - static func disableBurnOnQuitToEnableSessionRestore() -> String { - let localized = NSLocalizedString("disable.burn.on.quit.to.enable.session.restore", + static let autoClear = NSLocalizedString("auto.clear", value: "Auto-Clear", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.") + static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically clear browsing data when DuckDuckGo quits", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") + static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn me that data will be cleared when quitting", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.") + static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Clear browsing data and quit\nDuckDuckGo?", comment: "A header of warning before clearing data on the application termination.") + static let warnBeforeQuitDialogCheckboxMessage = NSLocalizedString("warn.before.quit.dialog.checkbox.message", value: "Warn me every time", comment: "A label after checkbox to configure the warning before clearing data on the application termination.") + + static func disableAutoClearToEnableSessionRestore() -> String { + let localized = NSLocalizedString("disable.auto.clear.to.enable.session.restore", value: "Disable the %@ setting to enable session restore on startup.", comment: "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit") - return String(format: localized, automaticallyClearData) + return String(format: localized, autoClear) } static func showDataClearingSettings() -> String { let localized = NSLocalizedString("show.data.clearing.settings", diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 39930f8a76..f5efea63d8 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -55,7 +55,7 @@ public struct UserDefaultsWrapper { case grammarCheckEnabledOnce = "grammar.check.enabled.once" case loginDetectionEnabled = "fireproofing.login-detection-enabled" - case burnDataOnQuitEnabled = "preferences.burn-data-on-quit-enabled" + case autoClearEnabled = "preferences.auto-clear-enabled" case warnBeforeClearingEnabled = "preferences.warn-before-clearing-enabled" case clearDataAfter = "preferences.clear-data-after" case gpcEnabled = "preferences.gpc-enabled" diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index ee449728e3..22779b61f8 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -2359,6 +2359,18 @@ } } }, + "auto.clear" : { + "comment" : "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Auto-Clear" + } + } + } + }, "autoconsent.checkbox.title" : { "comment" : "Autoconsent settings checkbox title", "extractionState" : "extracted_with_value", @@ -5600,13 +5612,13 @@ } }, "automatically.clear.data" : { - "comment" : "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.", + "comment" : "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.", "extractionState" : "extracted_with_value", "localizations" : { "en" : { "stringUnit" : { "state" : "new", - "value" : "Automatically Clear" + "value" : "Automatically clear browsing data when DuckDuckGo quits" } } } @@ -9880,30 +9892,6 @@ } } }, - "burn.data.on.quit" : { - "comment" : "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Clear Data upon Quitting" - } - } - } - }, - "burn.data.on.quit.explanation" : { - "comment" : "Explanation of a setting which configures clearing data automatically after quitting the app.", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Data from \"Fireproofed\" sites won't be cleared, keeping your essential logins and preferences intact." - } - } - } - }, "burner.homepage.description.1" : { "comment" : "Descriptions of features Fire page. Provides information about browsing functionalities such as browsing without saving local history, signing in to a site with a different account, and troubleshooting websites.", "extractionState" : "extracted_with_value", @@ -12848,7 +12836,7 @@ } } }, - "disable.burn.on.quit.to.enable.session.restore" : { + "disable.auto.clear.to.enable.session.restore" : { "comment" : "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit", "extractionState" : "extracted_with_value", "localizations" : { @@ -51713,31 +51701,31 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Warn before Quit" + "value" : "Warn me that data will be cleared when quitting" } } } }, - "warn.before.quit.dialog.header" : { - "comment" : "A header of warning before clearing data on the application termination.", + "warn.before.quit.dialog.checkbox.message" : { + "comment" : "A label after checkbox to configure the warning before clearing data on the application termination.", "extractionState" : "extracted_with_value", "localizations" : { "en" : { "stringUnit" : { "state" : "new", - "value" : "Quit the application and \nclear all data?" + "value" : "Warn me every time" } } } }, - "warn.before.quit.dialog.message" : { - "comment" : "A warning before clearing data on the application termination.", + "warn.before.quit.dialog.header" : { + "comment" : "A header of warning before clearing data on the application termination.", "extractionState" : "extracted_with_value", "localizations" : { "en" : { "stringUnit" : { "state" : "new", - "value" : "Cookies and site data for all sites will be cleared, unless the site is Fireproof." + "value" : "Clear browsing data and quit\nDuckDuckGo?" } } } diff --git a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift index 3c405140a5..0e7d7a59b1 100644 --- a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift @@ -20,7 +20,6 @@ import Foundation final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { - static let burnOnQuitNotificationKey = "isBurnDataOnQuitEnabled" static let shared = DataClearingPreferences() @Published @@ -31,10 +30,10 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { } @Published - var isBurnDataOnQuitEnabled: Bool { + var isAutoClearEnabled: Bool { didSet { - persistor.burnDataOnQuitEnabled = isBurnDataOnQuitEnabled - NotificationCenter.default.post(name: .burnDataOnQuitDidChange, + persistor.autoClearEnabled = isAutoClearEnabled + NotificationCenter.default.post(name: .autoClearDidChange, object: nil, userInfo: nil) } @@ -64,7 +63,7 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { init(persistor: FireButtonPreferencesPersistor = FireButtonPreferencesUserDefaultsPersistor()) { self.persistor = persistor isLoginDetectionEnabled = persistor.loginDetectionEnabled - isBurnDataOnQuitEnabled = persistor.burnDataOnQuitEnabled + isAutoClearEnabled = persistor.autoClearEnabled isWarnBeforeClearingEnabled = persistor.warnBeforeClearingEnabled } @@ -73,7 +72,7 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { protocol FireButtonPreferencesPersistor { var loginDetectionEnabled: Bool { get set } - var burnDataOnQuitEnabled: Bool { get set } + var autoClearEnabled: Bool { get set } var warnBeforeClearingEnabled: Bool { get set } } @@ -82,8 +81,8 @@ struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersisto @UserDefaultsWrapper(key: .loginDetectionEnabled, defaultValue: false) var loginDetectionEnabled: Bool - @UserDefaultsWrapper(key: .burnDataOnQuitEnabled, defaultValue: false) - var burnDataOnQuitEnabled: Bool + @UserDefaultsWrapper(key: .autoClearEnabled, defaultValue: false) + var autoClearEnabled: Bool @UserDefaultsWrapper(key: .warnBeforeClearingEnabled, defaultValue: false) var warnBeforeClearingEnabled: Bool @@ -91,5 +90,5 @@ struct FireButtonPreferencesUserDefaultsPersistor: FireButtonPreferencesPersisto } extension Notification.Name { - static let burnDataOnQuitDidChange = Notification.Name("burnDataOnQuitDidChange") + static let autoClearDidChange = Notification.Name("autoClearDidChange") } diff --git a/DuckDuckGo/Preferences/Model/StartupPreferences.swift b/DuckDuckGo/Preferences/Model/StartupPreferences.swift index 11ab60e384..9bdafbacf3 100644 --- a/DuckDuckGo/Preferences/Model/StartupPreferences.swift +++ b/DuckDuckGo/Preferences/Model/StartupPreferences.swift @@ -136,13 +136,13 @@ final class StartupPreferences: ObservableObject, PreferencesTabOpening { } private func checkDataClearingStatus() { - if dataClearingPreferences.isBurnDataOnQuitEnabled { + if dataClearingPreferences.isAutoClearEnabled { restorePreviousSession = false } } private func listenToDataClearingPreferencesNotifications() { - dataClearingPreferencesNotificationCancellable = NotificationCenter.default.publisher(for: .burnDataOnQuitDidChange).sink { [weak self] _ in + dataClearingPreferencesNotificationCancellable = NotificationCenter.default.publisher(for: .autoClearDidChange).sink { [weak self] _ in guard let self = self else { return } diff --git a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift index 0fb1e94feb..1680fb2e69 100644 --- a/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift @@ -29,16 +29,13 @@ extension Preferences { PreferencePane(UserText.dataClearing) { // SECTION 1: Automatically Clear Data - PreferencePaneSection(UserText.automaticallyClearData) { + PreferencePaneSection(UserText.autoClear) { PreferencePaneSubSection { - ToggleMenuItem(UserText.burnDataOnQuit, isOn: $model.isBurnDataOnQuitEnabled) - VStack(alignment: .leading, spacing: 1) { - TextMenuItemCaption(UserText.burnDataOnQuitExplanation) - } + ToggleMenuItem(UserText.automaticallyClearData, isOn: $model.isAutoClearEnabled) ToggleMenuItem(UserText.warnBeforeQuit, isOn: $model.isWarnBeforeClearingEnabled) - .disabled(!model.isBurnDataOnQuitEnabled) + .disabled(!model.isAutoClearEnabled) .padding(.leading, 16) } diff --git a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift index 5bf36add6f..528e920fc2 100644 --- a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift @@ -45,12 +45,12 @@ extension Preferences { .accessibilityIdentifier("PreferencesGeneralView.stateRestorePicker.reopenAllWindowsFromLastSession") }, label: {}) .pickerStyle(.radioGroup) - .disabled(dataClearingModel.isBurnDataOnQuitEnabled) + .disabled(dataClearingModel.isAutoClearEnabled) .offset(x: PreferencesViews.Const.pickerHorizontalOffset) .accessibilityIdentifier("PreferencesGeneralView.stateRestorePicker") - if dataClearingModel.isBurnDataOnQuitEnabled { + if dataClearingModel.isAutoClearEnabled { VStack(alignment: .leading, spacing: 1) { - TextMenuItemCaption(UserText.disableBurnOnQuitToEnableSessionRestore()) + TextMenuItemCaption(UserText.disableAutoClearToEnableSessionRestore()) TextButton(UserText.showDataClearingSettings()) { startupModel.show(url: .settingsPane(.dataClearing)) } diff --git a/UnitTests/AppDelegate/BurnOnQuitHandlerTests.swift b/UnitTests/AppDelegate/AutoClearHandlerTests.swift similarity index 82% rename from UnitTests/AppDelegate/BurnOnQuitHandlerTests.swift rename to UnitTests/AppDelegate/AutoClearHandlerTests.swift index 9a06bd3f0d..40545f1d3e 100644 --- a/UnitTests/AppDelegate/BurnOnQuitHandlerTests.swift +++ b/UnitTests/AppDelegate/AutoClearHandlerTests.swift @@ -1,5 +1,5 @@ // -// BurnOnQuitHandlerTests.swift +// AutoClearHandlerTests.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // @@ -22,9 +22,9 @@ import XCTest @testable import DuckDuckGo_Privacy_Browser @MainActor -class BurnOnQuitHandlerTests: XCTestCase { +class AutoClearHandlerTests: XCTestCase { - var handler: BurnOnQuitHandler! + var handler: AutoClearHandler! var preferences: DataClearingPreferences! var fireViewModel: FireViewModel! @@ -33,7 +33,7 @@ class BurnOnQuitHandlerTests: XCTestCase { let persistor = MockFireButtonPreferencesPersistor() preferences = DataClearingPreferences(persistor: persistor) fireViewModel = FireViewModel(fire: Fire(tld: ContentBlocking.shared.tld)) - handler = BurnOnQuitHandler(preferences: preferences, fireViewModel: fireViewModel) + handler = AutoClearHandler(preferences: preferences, fireViewModel: fireViewModel) } override func tearDown() { @@ -44,7 +44,7 @@ class BurnOnQuitHandlerTests: XCTestCase { } func testWhenBurningEnabledAndNoWarningRequiredThenTerminateLaterIsReturned() { - preferences.isBurnDataOnQuitEnabled = true + preferences.isAutoClearEnabled = true preferences.isWarnBeforeClearingEnabled = false let response = handler.handleAppTermination() @@ -53,7 +53,7 @@ class BurnOnQuitHandlerTests: XCTestCase { } func testWhenBurningDisabledThenNoTerminationResponse() { - preferences.isBurnDataOnQuitEnabled = false + preferences.isAutoClearEnabled = false let response = handler.handleAppTermination() @@ -61,14 +61,14 @@ class BurnOnQuitHandlerTests: XCTestCase { } func testWhenBurningEnabledAndFlagFalseThenBurnOnStartTriggered() { - preferences.isBurnDataOnQuitEnabled = true + preferences.isAutoClearEnabled = true handler.resetTheFlag() XCTAssertTrue(handler.burnOnStartIfNeeded()) } func testWhenBurningDisabledThenBurnOnStartNotTriggered() { - preferences.isBurnDataOnQuitEnabled = false + preferences.isAutoClearEnabled = false handler.resetTheFlag() XCTAssertFalse(handler.burnOnStartIfNeeded()) diff --git a/UnitTests/Preferences/DataClearingPreferencesTests.swift b/UnitTests/Preferences/DataClearingPreferencesTests.swift index 3fb71b91d6..1c0b6675e4 100644 --- a/UnitTests/Preferences/DataClearingPreferencesTests.swift +++ b/UnitTests/Preferences/DataClearingPreferencesTests.swift @@ -21,7 +21,7 @@ import XCTest class MockFireButtonPreferencesPersistor: FireButtonPreferencesPersistor { - var burnDataOnQuitEnabled: Bool = false + var autoClearEnabled: Bool = false var warnBeforeClearingEnabled: Bool = false var loginDetectionEnabled: Bool = false From c631833ed3114e2bb85822c33086a9300bace0e2 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Fri, 12 Apr 2024 10:01:13 +0200 Subject: [PATCH 15/22] Alert improved based on the ship review --- .../Common/Extensions/NSAlertExtension.swift | 24 +++++++++++++++++-- DuckDuckGo/Common/Localizables/UserText.swift | 1 + DuckDuckGo/Localizable.xcstrings | 12 ++++++++++ .../Model/DataClearingPreferences.swift | 4 ++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 4576a98515..84706ba09a 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -218,11 +218,31 @@ extension NSAlert { static func autoClearAlert() -> NSAlert { let alert = NSAlert() alert.messageText = UserText.warnBeforeQuitDialogHeader - //TODO checkbox alert.alertStyle = .warning alert.icon = .burnAlert - alert.addButton(withTitle: UserText.clear) + alert.addButton(withTitle: UserText.clearAndQuit) alert.addButton(withTitle: UserText.cancel) + + // Create a checkbox + let checkbox = NSButton(checkboxWithTitle: UserText.warnBeforeQuitDialogCheckboxMessage, + target: DataClearingPreferences.shared, + action: #selector(DataClearingPreferences.toggleWarnBeforeClearing)) + checkbox.state = DataClearingPreferences.shared.isWarnBeforeClearingEnabled ? .on : .off + checkbox.translatesAutoresizingMaskIntoConstraints = false + + // Create a container view for the checkbox with custom padding + let containerView = NSView(frame: NSRect(x: 0, y: 0, width: 224, height: 25)) + containerView.addSubview(checkbox) + + // Constraints for the checkbox within the container + NSLayoutConstraint.activate([ + checkbox.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), + checkbox.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: -10) // Slightly up for better visual alignment + ]) + + // Set the container view as the accessoryView + alert.accessoryView = containerView + return alert } diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 787ab1d4cc..d5aefcfdf4 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -53,6 +53,7 @@ struct UserText { static let pasteAndGo = NSLocalizedString("paste.and.go", value: "Paste & Go", comment: "Paste & Go button") static let pasteAndSearch = NSLocalizedString("paste.and.search", value: "Paste & Search", comment: "Paste & Search button") static let clear = NSLocalizedString("clear", value: "Clear", comment: "Clear button") + static let clearAndQuit = NSLocalizedString("clear.and.quit", value: "Clear and Quit", comment: "button to Clear data and quit the application") static let `continue` = NSLocalizedString("`continue`", value: "Continue", comment: "Continue button") static let bookmarkDialogAdd = NSLocalizedString("bookmark.dialog.add", value: "Add", comment: "Button to confim a bookmark creation") static let newFolderDialogAdd = NSLocalizedString("folder.dialog.add", value: "Add", comment: "Button to confim a bookmark folder creation") diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 22779b61f8..c271ecddb3 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -10718,6 +10718,18 @@ } } }, + "clear.and.quit" : { + "comment" : "button to Clear data and quit the application", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Clear and Quit" + } + } + } + }, "close.other.tabs" : { "comment" : "Menu item", "extractionState" : "extracted_with_value", diff --git a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift index 0e7d7a59b1..1d6106d195 100644 --- a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift @@ -46,6 +46,10 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { } } + @objc func toggleWarnBeforeClearing() { + isWarnBeforeClearingEnabled = !isWarnBeforeClearingEnabled + } + @MainActor func presentManageFireproofSitesDialog() { let fireproofDomainsWindowController = FireproofDomainsViewController.create().wrappedInWindowController() From 0a29cbbea4d9af164f35d9a05a35759263f948ad Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Fri, 12 Apr 2024 16:23:32 +0200 Subject: [PATCH 16/22] Quit without clearing --- DuckDuckGo/Application/AutoClearHandler.swift | 17 +++++++++++++---- .../Common/Extensions/NSAlertExtension.swift | 4 +--- DuckDuckGo/Common/Localizables/UserText.swift | 3 ++- DuckDuckGo/Localizable.xcstrings | 14 +++++++++++++- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/Application/AutoClearHandler.swift b/DuckDuckGo/Application/AutoClearHandler.swift index 1ed2bf4837..2d247cb21f 100644 --- a/DuckDuckGo/Application/AutoClearHandler.swift +++ b/DuckDuckGo/Application/AutoClearHandler.swift @@ -35,8 +35,17 @@ final class AutoClearHandler { func handleAppTermination() -> NSApplication.TerminateReply? { guard preferences.isAutoClearEnabled else { return nil } - if preferences.isWarnBeforeClearingEnabled, !confirmAutoClear() { - return .terminateCancel + if preferences.isWarnBeforeClearingEnabled { + switch confirmAutoClear() { + case .alertFirstButtonReturn: + performAutoClear() + return .terminateLater + case .alertSecondButtonReturn: + appTerminationHandledCorrectly = true + return .terminateNow + default: + return .terminateCancel + } } performAutoClear() @@ -49,10 +58,10 @@ final class AutoClearHandler { // MARK: - Private - private func confirmAutoClear() -> Bool { + private func confirmAutoClear() -> NSApplication.ModalResponse { let alert = NSAlert.autoClearAlert() let response = alert.runModal() - return response == .alertFirstButtonReturn + return response } @MainActor diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 84706ba09a..dcf298a733 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -221,9 +221,9 @@ extension NSAlert { alert.alertStyle = .warning alert.icon = .burnAlert alert.addButton(withTitle: UserText.clearAndQuit) + alert.addButton(withTitle: UserText.quitWithoutClearing) alert.addButton(withTitle: UserText.cancel) - // Create a checkbox let checkbox = NSButton(checkboxWithTitle: UserText.warnBeforeQuitDialogCheckboxMessage, target: DataClearingPreferences.shared, action: #selector(DataClearingPreferences.toggleWarnBeforeClearing)) @@ -234,13 +234,11 @@ extension NSAlert { let containerView = NSView(frame: NSRect(x: 0, y: 0, width: 224, height: 25)) containerView.addSubview(checkbox) - // Constraints for the checkbox within the container NSLayoutConstraint.activate([ checkbox.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), checkbox.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: -10) // Slightly up for better visual alignment ]) - // Set the container view as the accessoryView alert.accessoryView = containerView return alert diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index d5aefcfdf4..8b7d04b190 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -53,7 +53,8 @@ struct UserText { static let pasteAndGo = NSLocalizedString("paste.and.go", value: "Paste & Go", comment: "Paste & Go button") static let pasteAndSearch = NSLocalizedString("paste.and.search", value: "Paste & Search", comment: "Paste & Search button") static let clear = NSLocalizedString("clear", value: "Clear", comment: "Clear button") - static let clearAndQuit = NSLocalizedString("clear.and.quit", value: "Clear and Quit", comment: "button to Clear data and quit the application") + static let clearAndQuit = NSLocalizedString("clear.and.quit", value: "Clear and Quit", comment: "Button to clear data and quit the application") + static let quitWithoutClearing = NSLocalizedString("quit.without.clearing", value: "Quit without Clearing", comment: "Button to quit the application without clearing data") static let `continue` = NSLocalizedString("`continue`", value: "Continue", comment: "Continue button") static let bookmarkDialogAdd = NSLocalizedString("bookmark.dialog.add", value: "Add", comment: "Button to confim a bookmark creation") static let newFolderDialogAdd = NSLocalizedString("folder.dialog.add", value: "Add", comment: "Button to confim a bookmark folder creation") diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index c271ecddb3..2a77fa27ae 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -10719,7 +10719,7 @@ } }, "clear.and.quit" : { - "comment" : "button to Clear data and quit the application", + "comment" : "Button to clear data and quit the application", "extractionState" : "extracted_with_value", "localizations" : { "en" : { @@ -45765,6 +45765,18 @@ } } }, + "quit.without.clearing" : { + "comment" : "Button to quit the application without clearing data", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit without Clearing" + } + } + } + }, "Recently Closed" : { "comment" : "Main Menu History item", "localizations" : { From 2791d02b1324b3b23d6e38e5a88b031fd2c04e35 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Thu, 18 Apr 2024 14:08:27 +0200 Subject: [PATCH 17/22] Changes based on PR and ship review --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../xcschemes/sandbox-test-tool.xcscheme | 2 +- DuckDuckGo/Application/AppDelegate.swift | 7 ++-- DuckDuckGo/Application/AutoClearHandler.swift | 38 ++++++++++++++++++- .../Common/Extensions/NSAlertExtension.swift | 6 ++- DuckDuckGo/Common/Localizables/UserText.swift | 21 ++++------ .../Utilities/UserDefaultsWrapper.swift | 2 +- DuckDuckGo/Localizable.xcstrings | 10 ++--- .../Model/DataClearingPreferences.swift | 2 +- .../View/PreferencesGeneralView.swift | 4 +- .../StatePersistenceService.swift | 1 + .../AppDelegate/AutoClearHandlerTests.swift | 12 ++++-- 12 files changed, 72 insertions(+), 35 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1471db50fe..403ec8030a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme index 41730d7069..eb7e5e26bb 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/sandbox-test-tool.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> Void)? @@ -38,12 +49,16 @@ final class AutoClearHandler { if preferences.isWarnBeforeClearingEnabled { switch confirmAutoClear() { case .alertFirstButtonReturn: + // Clear and Quit performAutoClear() return .terminateLater case .alertSecondButtonReturn: + // Quit without Clearing Data appTerminationHandledCorrectly = true + restoreTabsOnStartup = true return .terminateNow default: + // Cancel return .terminateCancel } } @@ -52,7 +67,7 @@ final class AutoClearHandler { return .terminateLater } - func resetTheFlag() { + func resetTheCorrectTerminationFlag() { appTerminationHandledCorrectly = false } @@ -88,4 +103,23 @@ final class AutoClearHandler { return true } + // MARK: - Burn without Clearing Data + + @UserDefaultsWrapper(key: .restoreTabsOnStartup, defaultValue: false) + private var restoreTabsOnStartup: Bool + + @MainActor + @discardableResult + func restoreTabsIfNeeded() -> Bool { + let isAutoClearEnabled = preferences.isAutoClearEnabled + let restoreTabsOnStartup = restoreTabsOnStartup + self.restoreTabsOnStartup = false + if isAutoClearEnabled && restoreTabsOnStartup { + stateRestorationManager.restoreLastSessionState(interactive: false) + return true + } + + return false + } + } diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index dcf298a733..017389de08 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -228,15 +228,17 @@ extension NSAlert { target: DataClearingPreferences.shared, action: #selector(DataClearingPreferences.toggleWarnBeforeClearing)) checkbox.state = DataClearingPreferences.shared.isWarnBeforeClearingEnabled ? .on : .off + checkbox.lineBreakMode = .byWordWrapping checkbox.translatesAutoresizingMaskIntoConstraints = false // Create a container view for the checkbox with custom padding - let containerView = NSView(frame: NSRect(x: 0, y: 0, width: 224, height: 25)) + let containerView = NSView(frame: NSRect(x: 0, y: 0, width: 240, height: 25)) containerView.addSubview(checkbox) NSLayoutConstraint.activate([ checkbox.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), - checkbox.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: -10) // Slightly up for better visual alignment + checkbox.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: -10), // Slightly up for better visual alignment + checkbox.widthAnchor.constraint(lessThanOrEqualTo: containerView.widthAnchor) ]) alert.accessoryView = containerView diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 8b7d04b190..76d41212f4 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -54,7 +54,7 @@ struct UserText { static let pasteAndSearch = NSLocalizedString("paste.and.search", value: "Paste & Search", comment: "Paste & Search button") static let clear = NSLocalizedString("clear", value: "Clear", comment: "Clear button") static let clearAndQuit = NSLocalizedString("clear.and.quit", value: "Clear and Quit", comment: "Button to clear data and quit the application") - static let quitWithoutClearing = NSLocalizedString("quit.without.clearing", value: "Quit without Clearing", comment: "Button to quit the application without clearing data") + static let quitWithoutClearing = NSLocalizedString("quit.without.clearing", value: "Quit without Clearing Data", comment: "Button to quit the application without clearing data") static let `continue` = NSLocalizedString("`continue`", value: "Continue", comment: "Continue button") static let bookmarkDialogAdd = NSLocalizedString("bookmark.dialog.add", value: "Add", comment: "Button to confim a bookmark creation") static let newFolderDialogAdd = NSLocalizedString("folder.dialog.add", value: "Add", comment: "Button to confim a bookmark folder creation") @@ -1052,19 +1052,12 @@ struct UserText { static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn me that data will be cleared when quitting", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.") static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Clear browsing data and quit\nDuckDuckGo?", comment: "A header of warning before clearing data on the application termination.") static let warnBeforeQuitDialogCheckboxMessage = NSLocalizedString("warn.before.quit.dialog.checkbox.message", value: "Warn me every time", comment: "A label after checkbox to configure the warning before clearing data on the application termination.") - - static func disableAutoClearToEnableSessionRestore() -> String { - let localized = NSLocalizedString("disable.auto.clear.to.enable.session.restore", - value: "Disable the %@ setting to enable session restore on startup.", - comment: "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit") - return String(format: localized, autoClear) - } - static func showDataClearingSettings() -> String { - let localized = NSLocalizedString("show.data.clearing.settings", - value: "Show %@ Settings.", - comment: "Button in Settings. It navigates user to Data Clearing Settings.") - return String(format: localized, dataClearing) - } + static let disableAutoClearToEnableSessionRestore = NSLocalizedString("disable.auto.clear.to.enable.session.restore", + value: "Disable the Auto-Clear setting to enable session restore on startup.", + comment: "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit. Auto-Clear should match the string with 'auto.clear' key") + static let showDataClearingSettings = NSLocalizedString("show.data.clearing.settings", + value: "Show Data Clearing Settings.", + comment: "Button in Settings. It navigates user to Data Clearing Settings. The Data Clearing string should match the string with the preferences.data-clearing key") // MARK: Crash Report static let crashReportTitle = NSLocalizedString("crash-report.title", value: "DuckDuckGo Privacy Browser quit unexpectedly.", comment: "Title of the dialog where the user can send a crash report") diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index f5efea63d8..be5a626f29 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -57,7 +57,6 @@ public struct UserDefaultsWrapper { case loginDetectionEnabled = "fireproofing.login-detection-enabled" case autoClearEnabled = "preferences.auto-clear-enabled" case warnBeforeClearingEnabled = "preferences.warn-before-clearing-enabled" - case clearDataAfter = "preferences.clear-data-after" case gpcEnabled = "preferences.gpc-enabled" case selectedDownloadLocationKey = "preferences.download-location" case lastUsedCustomDownloadLocation = "preferences.custom-last-used-download-location" @@ -82,6 +81,7 @@ public struct UserDefaultsWrapper { case fireInfoPresentedOnce = "fire.info.presented.once" case appTerminationHandledCorrectly = "app.termination.handled.correctly" + case restoreTabsOnStartup = "restore.tabs.on.startup" case restorePreviousSession = "preferences.startup.restore-previous-session" case launchToCustomHomePage = "preferences.startup.launch-to-custom-home-page" diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 2a77fa27ae..b34189f2eb 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -12849,13 +12849,13 @@ } }, "disable.auto.clear.to.enable.session.restore" : { - "comment" : "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit", + "comment" : "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit. Auto-Clear should match the string with 'auto.clear' key", "extractionState" : "extracted_with_value", "localizations" : { "en" : { "stringUnit" : { "state" : "new", - "value" : "Disable the %@ setting to enable session restore on startup." + "value" : "Disable the Auto-Clear setting to enable session restore on startup." } } } @@ -45772,7 +45772,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Quit without Clearing" + "value" : "Quit without Clearing Data" } } } @@ -48397,13 +48397,13 @@ } }, "show.data.clearing.settings" : { - "comment" : "Button in Settings. It navigates user to Data Clearing Settings.", + "comment" : "Button in Settings. It navigates user to Data Clearing Settings. The Data Clearing string should match the string with the preferences.data-clearing key", "extractionState" : "extracted_with_value", "localizations" : { "en" : { "stringUnit" : { "state" : "new", - "value" : "Show %@ Settings." + "value" : "Show Data Clearing Settings." } } } diff --git a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift index 1d6106d195..26c2647981 100644 --- a/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DataClearingPreferences.swift @@ -47,7 +47,7 @@ final class DataClearingPreferences: ObservableObject, PreferencesTabOpening { } @objc func toggleWarnBeforeClearing() { - isWarnBeforeClearingEnabled = !isWarnBeforeClearingEnabled + isWarnBeforeClearingEnabled.toggle() } @MainActor diff --git a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift index 528e920fc2..ba9006a1ac 100644 --- a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift @@ -50,8 +50,8 @@ extension Preferences { .accessibilityIdentifier("PreferencesGeneralView.stateRestorePicker") if dataClearingModel.isAutoClearEnabled { VStack(alignment: .leading, spacing: 1) { - TextMenuItemCaption(UserText.disableAutoClearToEnableSessionRestore()) - TextButton(UserText.showDataClearingSettings()) { + TextMenuItemCaption(UserText.disableAutoClearToEnableSessionRestore) + TextButton(UserText.showDataClearingSettings) { startupModel.show(url: .settingsPane(.dataClearing)) } } diff --git a/DuckDuckGo/StateRestoration/StatePersistenceService.swift b/DuckDuckGo/StateRestoration/StatePersistenceService.swift index 6aed78934a..5d65a2c1d0 100644 --- a/DuckDuckGo/StateRestoration/StatePersistenceService.swift +++ b/DuckDuckGo/StateRestoration/StatePersistenceService.swift @@ -65,6 +65,7 @@ final class StatePersistenceService { func removeLastSessionState() { lastSessionStateArchive = nil + fileStore.remove(fileAtURL: URL.persistenceLocation(for: self.fileName)) } @MainActor diff --git a/UnitTests/AppDelegate/AutoClearHandlerTests.swift b/UnitTests/AppDelegate/AutoClearHandlerTests.swift index 40545f1d3e..192de793f3 100644 --- a/UnitTests/AppDelegate/AutoClearHandlerTests.swift +++ b/UnitTests/AppDelegate/AutoClearHandlerTests.swift @@ -33,7 +33,13 @@ class AutoClearHandlerTests: XCTestCase { let persistor = MockFireButtonPreferencesPersistor() preferences = DataClearingPreferences(persistor: persistor) fireViewModel = FireViewModel(fire: Fire(tld: ContentBlocking.shared.tld)) - handler = AutoClearHandler(preferences: preferences, fireViewModel: fireViewModel) + let fileName = "AutoClearHandlerTests" + let fileStore = FileStoreMock() + let service = StatePersistenceService(fileStore: fileStore, fileName: fileName) + let appStateRestorationManager = AppStateRestorationManager(fileStore: fileStore, + service: service, + shouldRestorePreviousSession: false) + handler = AutoClearHandler(preferences: preferences, fireViewModel: fireViewModel, stateRestorationManager: appStateRestorationManager) } override func tearDown() { @@ -62,14 +68,14 @@ class AutoClearHandlerTests: XCTestCase { func testWhenBurningEnabledAndFlagFalseThenBurnOnStartTriggered() { preferences.isAutoClearEnabled = true - handler.resetTheFlag() + handler.resetTheCorrectTerminationFlag() XCTAssertTrue(handler.burnOnStartIfNeeded()) } func testWhenBurningDisabledThenBurnOnStartNotTriggered() { preferences.isAutoClearEnabled = false - handler.resetTheFlag() + handler.resetTheCorrectTerminationFlag() XCTAssertFalse(handler.burnOnStartIfNeeded()) } From 3cf32e4698023673d3a9bcbf0c7cf12df48f387e Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Fri, 19 Apr 2024 11:10:54 +0200 Subject: [PATCH 18/22] Icon replaced --- .../BurnAlert.imageset/Burn-Original-large.pdf | Bin 43425 -> 0 bytes .../Images/BurnAlert.imageset/Contents.json | 2 +- .../Images/BurnAlert.imageset/Fire-96x96.pdf | Bin 0 -> 17864 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Burn-Original-large.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Fire-96x96.pdf diff --git a/DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Burn-Original-large.pdf b/DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Burn-Original-large.pdf deleted file mode 100644 index 2208d560684cf0225cd340b626bb4ff0110e1e21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43425 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 O2(Me8J`%OG+y4XduDN0W diff --git a/DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Contents.json index 48111f0d3f..3f5be128a3 100644 --- a/DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Contents.json +++ b/DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Burn-Original-large.pdf", + "filename" : "Fire-96x96.pdf", "idiom" : "universal" } ], diff --git a/DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Fire-96x96.pdf b/DuckDuckGo/Assets.xcassets/Images/BurnAlert.imageset/Fire-96x96.pdf new file mode 100644 index 0000000000000000000000000000000000000000..297359ff71c645b5cf4fc43b5e3171962c063b8a GIT binary patch literal 17864 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 kG~P*9b@bxwFQ>UlvT=2Ne0g2&R!}>&oA=)P Date: Mon, 22 Apr 2024 14:38:47 +0200 Subject: [PATCH 19/22] Copy changed --- DuckDuckGo/Common/Localizables/UserText.swift | 6 +++--- DuckDuckGo/Localizable.xcstrings | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 7e470b579b..80f82564af 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -54,7 +54,7 @@ struct UserText { static let pasteAndSearch = NSLocalizedString("paste.and.search", value: "Paste & Search", comment: "Paste & Search button") static let clear = NSLocalizedString("clear", value: "Clear", comment: "Clear button") static let clearAndQuit = NSLocalizedString("clear.and.quit", value: "Clear and Quit", comment: "Button to clear data and quit the application") - static let quitWithoutClearing = NSLocalizedString("quit.without.clearing", value: "Quit without Clearing Data", comment: "Button to quit the application without clearing data") + static let quitWithoutClearing = NSLocalizedString("quit.without.clearing", value: "Quit Without Clearing Data", comment: "Button to quit the application without clearing data") static let `continue` = NSLocalizedString("`continue`", value: "Continue", comment: "Continue button") static let bookmarkDialogAdd = NSLocalizedString("bookmark.dialog.add", value: "Add", comment: "Button to confim a bookmark creation") static let newFolderDialogAdd = NSLocalizedString("folder.dialog.add", value: "Add", comment: "Button to confim a bookmark folder creation") @@ -1086,10 +1086,10 @@ struct UserText { static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Clear browsing data and quit\nDuckDuckGo?", comment: "A header of warning before clearing data on the application termination.") static let warnBeforeQuitDialogCheckboxMessage = NSLocalizedString("warn.before.quit.dialog.checkbox.message", value: "Warn me every time", comment: "A label after checkbox to configure the warning before clearing data on the application termination.") static let disableAutoClearToEnableSessionRestore = NSLocalizedString("disable.auto.clear.to.enable.session.restore", - value: "Disable the Auto-Clear setting to enable session restore on startup.", + value: "Disable auto-clear on quit to turn on session restore.", comment: "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit. Auto-Clear should match the string with 'auto.clear' key") static let showDataClearingSettings = NSLocalizedString("show.data.clearing.settings", - value: "Show Data Clearing Settings.", + value: "Open Data Clearing Settings", comment: "Button in Settings. It navigates user to Data Clearing Settings. The Data Clearing string should match the string with the preferences.data-clearing key") // MARK: Crash Report diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 21ffebae88..b435490a9c 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -12860,7 +12860,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Disable the Auto-Clear setting to enable session restore on startup." + "value" : "Disable auto-clear on quit to turn on session restore." } } } @@ -45874,7 +45874,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Quit without Clearing Data" + "value" : "Quit Without Clearing Data" } } } @@ -48506,7 +48506,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Data Clearing Settings." + "value" : "Open Data Clearing Settings" } } } From 97611055dbdcd0f59678caaa7b29062970482095 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Tue, 23 Apr 2024 11:08:07 +0200 Subject: [PATCH 20/22] More copy changed --- DuckDuckGo/Common/Localizables/UserText.swift | 8 ++++---- DuckDuckGo/Localizable.xcstrings | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 80f82564af..8be4a25b6b 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -54,7 +54,7 @@ struct UserText { static let pasteAndSearch = NSLocalizedString("paste.and.search", value: "Paste & Search", comment: "Paste & Search button") static let clear = NSLocalizedString("clear", value: "Clear", comment: "Clear button") static let clearAndQuit = NSLocalizedString("clear.and.quit", value: "Clear and Quit", comment: "Button to clear data and quit the application") - static let quitWithoutClearing = NSLocalizedString("quit.without.clearing", value: "Quit Without Clearing Data", comment: "Button to quit the application without clearing data") + static let quitWithoutClearing = NSLocalizedString("quit.without.clearing", value: "Quit Without Clearing", comment: "Button to quit the application without clearing data") static let `continue` = NSLocalizedString("`continue`", value: "Continue", comment: "Continue button") static let bookmarkDialogAdd = NSLocalizedString("bookmark.dialog.add", value: "Add", comment: "Button to confim a bookmark creation") static let newFolderDialogAdd = NSLocalizedString("folder.dialog.add", value: "Add", comment: "Button to confim a bookmark folder creation") @@ -1081,9 +1081,9 @@ struct UserText { static let fireproofExplanation = NSLocalizedString("fireproof.explanation", value: "When you Fireproof a site, cookies won't be erased and you'll stay signed in, even after using the Fire Button.", comment: "Fireproofing mechanism explanation") static let manageFireproofSites = NSLocalizedString("fireproof.manage-sites", value: "Manage Fireproof Sites…", comment: "Fireproof settings button caption") static let autoClear = NSLocalizedString("auto.clear", value: "Auto-Clear", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.") - static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically clear browsing data when DuckDuckGo quits", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") - static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn me that data will be cleared when quitting", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.") - static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Clear browsing data and quit\nDuckDuckGo?", comment: "A header of warning before clearing data on the application termination.") + static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically clear tabs and browsing data when DuckDuckGo quits", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.") + static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn me that tabs and data will be cleared when quitting", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.") + static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Clear tabs and browsing data and quit DuckDuckGo?", comment: "A header of warning before clearing data on the application termination.") static let warnBeforeQuitDialogCheckboxMessage = NSLocalizedString("warn.before.quit.dialog.checkbox.message", value: "Warn me every time", comment: "A label after checkbox to configure the warning before clearing data on the application termination.") static let disableAutoClearToEnableSessionRestore = NSLocalizedString("disable.auto.clear.to.enable.session.restore", value: "Disable auto-clear on quit to turn on session restore.", diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index b435490a9c..a6c4b37002 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -5620,7 +5620,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Automatically clear browsing data when DuckDuckGo quits" + "value" : "Automatically clear tabs and browsing data when DuckDuckGo quits" } } } @@ -45874,7 +45874,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Quit Without Clearing Data" + "value" : "Quit Without Clearing" } } } @@ -52548,7 +52548,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Warn me that data will be cleared when quitting" + "value" : "Warn me that tabs and data will be cleared when quitting" } } } @@ -52572,7 +52572,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear browsing data and quit\nDuckDuckGo?" + "value" : "Clear tabs and browsing data and quit DuckDuckGo?" } } } From 2e79aa4d604cdf8e648e8750d23a62995814ce70 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Thu, 25 Apr 2024 10:59:55 +0200 Subject: [PATCH 21/22] Design review --- DuckDuckGo/Preferences/View/PreferencesGeneralView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift index 7e68765275..7d22bc8867 100644 --- a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift @@ -55,6 +55,7 @@ extension Preferences { startupModel.show(url: .settingsPane(.dataClearing)) } } + .padding(.leading, 19) } } } From b19a2751807fb1f8986727f72861d12fb74117b1 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Sun, 28 Apr 2024 21:06:52 +0200 Subject: [PATCH 22/22] Translations --- DuckDuckGo/Localizable.xcstrings | 664 +++++++++++++++++- .../SyncUI/Resources/Localizable.xcstrings | 146 +++- 2 files changed, 789 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index ce88aaaf6c..584e2ceadc 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -482,10 +482,56 @@ } }, "1." : { - - }, - "2. Open the downloaded DMG file and drag the Bitwarden application to\nthe /Applications folder." : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "1." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "1." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "1." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "1." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "1." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "1." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "1." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "1." + } + } + } }, "about.app_name" : { "comment" : "Application name to be displayed in the About dialog", @@ -2371,11 +2417,59 @@ "comment" : "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisch löschen" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Auto-Clear" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Borrado automático" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Effacement automatique" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancellazione automatica" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisch wissen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatyczne czyszczenie" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limpeza automática" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоочистка" + } } } }, @@ -5623,11 +5717,59 @@ "comment" : "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tabs und Browserdaten automatisch löschen, wenn DuckDuckGo beendet wird" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Automatically clear tabs and browsing data when DuckDuckGo quits" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Borra automáticamente las pestañas y los datos de navegación cuando se cierra DuckDuckGo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Effacer automatiquement les onglets et les données de navigation lorsque DuckDuckGo se ferme" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancella automaticamente schede e dati di navigazione quando chiudi DuckDuckGo" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tabbladen en browsegegevens automatisch wissen wanneer DuckDuckGo stopt" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatycznie czyść karty i dane przeglądania przy wychodzeniu z DuckDuckGo" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limpar automaticamente os separadores e os dados de navegação quando o DuckDuckGo fecha" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматически закрывает вкладки и стирает данные о посещении сайтов при завершении работы DuckDuckGo." + } } } }, @@ -6287,11 +6429,59 @@ "comment" : "Message that warns user that specific Bitwarden app vesions are not compatible with this app", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die folgenden Bitwarden-Versionen sind mit DuckDuckGo inkompatibel: v2024.3.0, v2024.3.2, v2024.4.0, v2024.4.1. Bitte kehre zu einer älteren Version zurück, indem du die folgenden Schritte ausführst:" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "The following Bitwarden versions are incompatible with DuckDuckGo: v2024.3.0, v2024.3.2, v2024.4.0, v2024.4.1. Please revert to an older version by following these steps:" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Las siguientes versiones de Bitwarden son incompatibles con DuckDuckGo: v2024.3.0, v2024.3.2, v2024.4.0, v2024.4.1. Vuelve a una versión anterior siguiendo estos pasos:" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les versions suivantes de Bitwarden sont incompatibles avec DuckDuckGo : v2024.3.0, v2024.3.2, v2024.4.0 et v2024.4.1. Veuillez revenir à une version antérieure en procédant comme suit :" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Le seguenti versioni di Bitwarden non sono compatibili con DuckDuckGo: v2024.3.0, v2024.3.2, v2024.4.0, v2024.4.1. È necessario tornare a una versione precedente seguendo questi passaggi:" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "De volgende versies van Bitwarden zijn niet compatibel met DuckDuckGo: v2024.3.0, v2024.3.2, v2024.4.0, v2024.4.1. Volg deze stappen om terug te gaan naar een oudere versie:" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Następujące wersje Bitwarden są niezgodne z DuckDuckGo: v2024.3.0, v2024.3.2, v2024.4.0, v2024.4.1. Przywróć starszą wersję, wykonując następujące czynności:" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "As seguintes versões do Bitwarden são incompatíveis com o DuckDuckGo: v2024.3.0, v2024.3.2, v2024.4.0 e v2024.4.1. Reverte para uma versão mais antiga seguindo estes passos:" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "С DuckDuckGo несовместимы следующие версии Bitwarden: 2024.3.0, 2024.3.2, 2024.4.0, 2024.4.1. Вернитесь к более старой версии, выполнив следующие действия:" + } } } }, @@ -6299,11 +6489,59 @@ "comment" : "First step to downgrade Bitwarden", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "V2014.2.1 herunterladen" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Download v2014.2.1" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Descargar v2014.2.1" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Télécharger v2014.2.1" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarica la versione v2014.2.1" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Download v2014.2.1" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pobierz v2014.2.1" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Transfere a versão v2014.2.1" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скачайте версию 2014.2.1" + } } } }, @@ -6311,11 +6549,59 @@ "comment" : "Second step to downgrade Bitwarden", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. Öffne die heruntergeladene DMG-Datei und ziehe die Bitwarden-Anwendung auf den Ordner „/Applications“." + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "2. Open the downloaded DMG file and drag the Bitwarden application to\nthe /Applications folder." } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. Abre el archivo DMG descargado y arrastra la aplicación Bitwarden a\nla carpeta /Applications." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. Ouvrez le fichier DMG téléchargé et faites glisser l'application Bitwarden vers\nle dossier /Applications." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. Apri il file DMG scaricato e trascina l'applicazione Bitwarden nella\n/cartella Applications." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. Open het gedownloade DMG-bestand en sleep de Bitwarden-app naar\n de map /Applications." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. Otwórz pobrany plik DMG i przeciągnij aplikację Bitwarden do\nfolderu /Applications." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. Abre o ficheiro DMG transferido e arrasta a aplicação Bitwarden para\na pasta /Applications." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. Откройте загруженный файл DMG и перетащите приложение Bitwarden в стандартную папку «Программы»." + } } } }, @@ -10767,11 +11053,59 @@ "comment" : "Button to clear data and quit the application", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Löschen und Beenden" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Clear and Quit" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Borrar y salir" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Effacer et quitter" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancella ed esci" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wissen en stoppen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyczyść i wyjdź" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limpar e sair" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очистить и выйти" + } } } }, @@ -12899,11 +13233,59 @@ "comment" : "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit. Auto-Clear should match the string with 'auto.clear' key", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deaktiviere das automatische Löschen beim Beenden, um die Sitzungswiederherstellung einzuschalten." + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Disable auto-clear on quit to turn on session restore." } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desactiva el borrado automático al salir para activar Restaurar sesión." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Désactivez l'effacement automatique à la fermeture pour activer la restauration de session." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Disabilita la cancellazione automatica all'uscita per attivare il ripristino della sessione." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schakel automatisch wissen uit wanneer u stopt om het herstellen van de sessie in te schakelen." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyłącz automatyczne czyszczenie przy wychodzeniu, aby włączyć przywracanie sesji." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desativa a limpeza automática ao sair para ativar a restauração da sessão." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чтобы активировать восстановление сеанса, отключите автоочистку данных при выходе." + } } } }, @@ -13378,9 +13760,6 @@ } } } - }, - "Download v2014.2.1" : { - }, "download.finishing" : { "comment" : "Download being finished information text", @@ -20108,6 +20487,7 @@ }, "Hide" : { "comment" : "Main Menu > View > Home Button > None item\n Preferences > Home Button > None item", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -24478,7 +24858,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если у вас есть закладки в %@, попробуйте импортировать их вручную." + "value" : "Если у вас есть %@-файл с закладками, попробуйте импортировать его вручную." } } } @@ -24538,7 +24918,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если у вас есть пароли в %@, попробуйте импортировать их вручную." + "value" : "Если у вас есть %@-файл с паролями, попробуйте импортировать его вручную." } } } @@ -30421,7 +30801,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Teile deine Gedanken" + "value" : "Sign Up To Participate" } }, "en" : { @@ -30481,7 +30861,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nimm an unserer kurzen Umfrage teil und hilf uns, den besten Browser zu entwickeln." + "value" : "Join an interview with a member of our research team to help us build the best browser." } }, "en" : { @@ -30541,7 +30921,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sag uns, was dich hierher gebracht hat" + "value" : "Share Your Thoughts With Us" } }, "en" : { @@ -30781,7 +31161,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hilf uns, uns zu verbessern" + "value" : "Sign Up To Participate" } }, "en" : { @@ -30913,43 +31293,43 @@ "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ayúdanos a mejorar" + "value" : "Share Your Thoughts With Us" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aidez-nous à nous améliorer" + "value" : "Share Your Thoughts With Us" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aiutaci a migliorare" + "value" : "Share Your Thoughts With Us" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Help ons om te verbeteren" + "value" : "Share Your Thoughts With Us" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pomóż nam we wprowadzaniu ulepszeń" + "value" : "Share Your Thoughts With Us" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ajuda-nos a melhorar" + "value" : "Share Your Thoughts With Us" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Помогите нам стать лучше" + "value" : "Share Your Thoughts With Us" } } } @@ -45916,11 +46296,59 @@ "comment" : "Button to quit the application without clearing data", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beenden ohne Löschen" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Quit Without Clearing" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salir sin borrar" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quitter sans effacer" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esci senza cancellare" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stoppen zonder wissen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyjdź bez czyszczenia" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sair sem limpar" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выйти без очистки" + } } } }, @@ -48069,6 +48497,7 @@ }, "Show left of the back button" : { "comment" : "Preferences > Home Button > left position item", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -48122,6 +48551,7 @@ }, "Show Left of the Back Button" : { "comment" : "Main Menu > View > Home Button > left position item", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -48387,6 +48817,7 @@ }, "Show right of the reload button" : { "comment" : "Preferences > Home Button > right position item", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -48440,6 +48871,7 @@ }, "Show Right of the Reload Button" : { "comment" : "Main Menu > View > Home Button > right position item", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -48548,11 +48980,59 @@ "comment" : "Button in Settings. It navigates user to Data Clearing Settings. The Data Clearing string should match the string with the preferences.data-clearing key", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einstellungen zum Löschen von Daten öffnen" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Open Data Clearing Settings" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir configuración de borrado de datos" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir les paramètres d'effacement des données" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apri Impostazioni cancellazione dati" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instellingen voor het wissen van open gegevens" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otwórz ustawienia czyszczenia danych" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir Definições de limpeza de dados" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открыть настройки очистки данных" + } } } }, @@ -52590,11 +53070,59 @@ "comment" : "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Warne mich, dass Tabs und Daten beim Beenden gelöscht werden" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Warn me that tabs and data will be cleared when quitting" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Advertirme de que las pestañas y los datos se borrarán al salir" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "M'avertir que les onglets et les données seront effacés à la fermeture" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avvisa prima di cancellare schede e dati all'uscita" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Waarschuwen dat tabbladen en gegevens worden gewist bij het afsluiten" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ostrzegaj, że karty i dane zostaną wyczyszczone przy wychodzeniu" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avisar-me que os separadores e os dados serão limpos ao sair" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывать предупреждение о сбросе вкладок и данных при выходе" + } } } }, @@ -52602,11 +53130,59 @@ "comment" : "A label after checkbox to configure the warning before clearing data on the application termination.", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jedes Mal warnen" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Warn me every time" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Advertirme cada vez" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toujours me prévenir" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avvisa ogni volta" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elke keer waarschuwen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ostrzegaj za każdym razem" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avisar-me sempre" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предупреждать каждый раз" + } } } }, @@ -52614,11 +53190,59 @@ "comment" : "A header of warning before clearing data on the application termination.", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tabs und Browserdaten löschen und DuckDuckGo beenden?" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Clear tabs and browsing data and quit DuckDuckGo?" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Borrar pestañas y datos de navegación y salir de DuckDuckGo?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Effacer les onglets et les données de navigation et quitter DuckDuckGo ?" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancellare le schede e i dati di navigazione e uscire da DuckDuckGo?" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tabbladen en browsergegevens wissen en DuckDuckGo afsluiten?" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyczyścić karty i dane przeglądania i wyjść z DuckDuckGo?" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limpar separadores e dados de navegação e sair do DuckDuckGo?" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очистить вкладки и данные и выйти из DuckDuckGo?" + } } } }, diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Resources/Localizable.xcstrings b/LocalPackages/SyncUI/Sources/SyncUI/Resources/Localizable.xcstrings index 4abefdc849..dc53430490 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Resources/Localizable.xcstrings +++ b/LocalPackages/SyncUI/Sources/SyncUI/Resources/Localizable.xcstrings @@ -123,11 +123,59 @@ "comment" : "Title for an error alert", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync & Backup Error" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Sync & Backup Error" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync & Backup Error" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync & Backup Error" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync & Backup Error" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync & Backup Error" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync & Backup Error" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync & Backup Error" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sync & Backup Error" + } } } }, @@ -135,11 +183,59 @@ "comment" : "Button Title of an error alert", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to Settings" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Go to Settings" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to Settings" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to Settings" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to Settings" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to Settings" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to Settings" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to Settings" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to Settings" + } } } }, @@ -207,11 +303,59 @@ "comment" : "Description for unable to authenticate error", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "A device password is required to use Sync & Backup." + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "A device password is required to use Sync & Backup." } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "A device password is required to use Sync & Backup." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "A device password is required to use Sync & Backup." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "A device password is required to use Sync & Backup." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "A device password is required to use Sync & Backup." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "A device password is required to use Sync & Backup." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "A device password is required to use Sync & Backup." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "A device password is required to use Sync & Backup." + } } } }, @@ -6277,4 +6421,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file