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) } }