From 53fd67c4fe111a7bf4ff5885717897168bb953e4 Mon Sep 17 00:00:00 2001 From: Anya Mallon Date: Wed, 8 May 2024 13:51:41 +0200 Subject: [PATCH 1/6] Password survey added to Settings screen --- .../xcshareddata/swiftpm/Package.resolved | 7 ++-- .../Passwords-DDG-128.imageset/Contents.json | 12 ++++++ .../Passwords-DDG-128.svg | 31 +++++++++++++++ .../Utilities/UserDefaultsWrapper.swift | 1 + .../Model/AutofillPreferences.swift | 4 ++ .../Model/AutofillPreferencesModel.swift | 38 +++++++++++++++++++ .../View/PreferencesAutofillView.swift | 37 ++++++++++++++++++ 7 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/Images/Autofill/Passwords-DDG-128.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/Autofill/Passwords-DDG-128.imageset/Passwords-DDG-128.svg diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index dd9eae0f55..05a2fe5618 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "f34b0a63938df11ef471aa3301dcc0de09b0d31b", - "version" : "144.0.1" + "revision" : "267927e389f3992ce782224f90a4db1ff3ea9730" } }, { @@ -50,8 +49,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "6053999d6af384a716ab0ce7205dbab5d70ed1b3", - "version" : "11.0.1" + "revision" : "10aeff1ec7f533d1705233a9b14f9393a699b1c0", + "version" : "11.0.2" } }, { diff --git a/DuckDuckGo/Assets.xcassets/Images/Autofill/Passwords-DDG-128.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/Autofill/Passwords-DDG-128.imageset/Contents.json new file mode 100644 index 0000000000..1c9ce942d3 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/Autofill/Passwords-DDG-128.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Passwords-DDG-128.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/Autofill/Passwords-DDG-128.imageset/Passwords-DDG-128.svg b/DuckDuckGo/Assets.xcassets/Images/Autofill/Passwords-DDG-128.imageset/Passwords-DDG-128.svg new file mode 100644 index 0000000000..fefd0c6886 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/Autofill/Passwords-DDG-128.imageset/Passwords-DDG-128.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 5891b891a5..5876bef57d 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -74,6 +74,7 @@ public struct UserDefaultsWrapper { case askToSavePaymentMethods = "preferences.ask-to-save.payment-methods" case autolockLocksFormFilling = "preferences.lock-autofill-form-fill" case autofillDebugScriptEnabled = "preferences.enable-autofill-debug-script" + case autofillSurveyEnabled = "preferences.enable-autofill-survey" case saveAsPreferredFileType = "saveAs.selected.filetype" diff --git a/DuckDuckGo/Preferences/Model/AutofillPreferences.swift b/DuckDuckGo/Preferences/Model/AutofillPreferences.swift index 58a55e86f1..78920dd0c0 100644 --- a/DuckDuckGo/Preferences/Model/AutofillPreferences.swift +++ b/DuckDuckGo/Preferences/Model/AutofillPreferences.swift @@ -27,6 +27,7 @@ protocol AutofillPreferencesPersistor { var autolockLocksFormFilling: Bool { get set } var passwordManager: PasswordManager { get set } var debugScriptEnabled: Bool { get set } + var autofillSurveyEnabled: Bool { get set } } enum PasswordManager: String, CaseIterable { @@ -149,6 +150,9 @@ final class AutofillPreferences: AutofillPreferencesPersistor { } } + @UserDefaultsWrapper(key: .autofillSurveyEnabled, defaultValue: true) + var autofillSurveyEnabled: Bool + private var statisticsStore: StatisticsStore { return injectedDependencyStore ?? defaultDependencyStore } diff --git a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift index 4f254b3a0f..c1ee576128 100644 --- a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift @@ -17,6 +17,8 @@ // import Foundation +import BrowserServicesKit +import Common final class AutofillPreferencesModel: ObservableObject { @@ -59,6 +61,12 @@ final class AutofillPreferencesModel: ObservableObject { } } + @Published private(set) var autofillSurveyEnabled: Bool { + didSet { + persistor.autofillSurveyEnabled = autofillSurveyEnabled && Bundle.main.preferredLocalizations.first == "en" + } + } + @MainActor @Published private(set) var passwordManager: PasswordManager { didSet { @@ -153,6 +161,7 @@ final class AutofillPreferencesModel: ObservableObject { autolockLocksFormFilling = persistor.autolockLocksFormFilling passwordManager = persistor.passwordManager hasNeverPromptWebsites = !neverPromptWebsitesManager.neverPromptWebsites.isEmpty + autofillSurveyEnabled = persistor.autofillSurveyEnabled } private var persistor: AutofillPreferencesPersistor @@ -192,4 +201,33 @@ final class AutofillPreferencesModel: ObservableObject { NSWorkspace.shared.open(.fullDiskAccess) } + func launchSurvey(statisticsStore: StatisticsStore = LocalStatisticsStore(), + activationDateStore: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .netP), + operatingSystemVersion: String = ProcessInfo.processInfo.operatingSystemVersion.description, + appVersion: String = AppVersion.shared.versionNumber, + hardwareModel: String? = HardwareModel.model) { + + let surveyURLBuilder = SurveyURLBuilder( + statisticsStore: statisticsStore, + operatingSystemVersion: operatingSystemVersion, + appVersion: appVersion, + hardwareModel: hardwareModel, + daysSinceActivation: activationDateStore.daysSinceActivation(), + daysSinceLastActive: activationDateStore.daysSinceLastActive() + ) + + guard let surveyUrl = surveyURLBuilder.buildSurveyURL(from: "https://selfserve.decipherinc.com/survey/selfserve/32ab/240307") else { + return + } + + DispatchQueue.main.async { + WindowControllersManager.shared.showTab(with: .url(surveyUrl, credential: nil, source: .appOpenUrl)) + } + + disableAutofillSurvey() + } + + func disableAutofillSurvey() { + autofillSurveyEnabled = false + } } diff --git a/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift b/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift index cb36a0d227..d3e4501562 100644 --- a/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesAutofillView.swift @@ -67,6 +67,43 @@ extension Preferences { // Autofill Content Button PreferencePaneSection { + + // New section + if model.autofillSurveyEnabled { + HStack(alignment: .top, spacing: 20) { + Image(.passwordsDDG128) + .frame(width: 64, height: 48) + + VStack(alignment: .leading) { + Text(verbatim: "Help us improve!") + .bold() + Text(verbatim: "We want to make using passwords in DuckDuckGo better.") + .foregroundColor(.greyText) + .padding(.top, 1) + + HStack { + Button(action: { + model.disableAutofillSurvey() + }, label: { + Text(verbatim: "No Thanks") + }) + Button(action: { + model.launchSurvey() + }, label: { + Text(verbatim: "Take Survey") + }) + .buttonStyle(DefaultActionButtonStyle(enabled: true)) + } + .padding(.top, 12) + } + + Spacer() + } + .padding() + .roundedBorder() + .padding(.bottom, 24) + } + Button(UserText.autofillViewContentButton) { model.showAutofillPopover() } From f5b777b0b0b5864cbdac8b43ab026a0721e45055 Mon Sep 17 00:00:00 2001 From: Anya Mallon Date: Wed, 8 May 2024 13:51:56 +0200 Subject: [PATCH 2/6] Debug menu option to reset survey prompt --- DuckDuckGo/Menus/MainMenu.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 66e026dc1d..1b24150862 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -584,6 +584,7 @@ import SubscriptionUI } NSMenuItem(title: "Reset Email Protection InContext Signup Prompt", action: #selector(MainViewController.resetEmailProtectionInContextPrompt)) NSMenuItem(title: "Reset Pixels Storage", action: #selector(MainViewController.resetDailyPixels)) + NSMenuItem(title: "Reset Passwords Survey", action: #selector(enablePasswordsSurveyAction), target: self) }.withAccessibilityIdentifier("MainMenu.resetData") NSMenuItem(title: "UI Triggers") { NSMenuItem(title: "Show Save Credentials Popover", action: #selector(MainViewController.showSaveCredentialsPopover)) @@ -729,6 +730,10 @@ import SubscriptionUI updateAutofillDebugScriptMenuItem() } + @objc private func enablePasswordsSurveyAction(_ sender: NSMenuItem) { + AutofillPreferences().autofillSurveyEnabled = true + } + @objc private func debugLoggingMenuItemAction(_ sender: NSMenuItem) { #if APPSTORE if !OSLog.isRunningInDebugEnvironment { From f782b5483bc74f3e024e5f327f27e40276e29edb Mon Sep 17 00:00:00 2001 From: Anya Mallon Date: Wed, 8 May 2024 14:12:54 +0200 Subject: [PATCH 3/6] Add saved_passwords bucket parameter to survey --- DuckDuckGo.xcodeproj/project.pbxproj | 4 +-- .../Common/Surveys/SurveyURLBuilder.swift | 27 +++++++++++++++++++ .../Model/AutofillPreferencesModel.swift | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a5a233c122..04720a2613 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12777,8 +12777,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 144.0.1; + kind = revision; + revision = 267927e389f3992ce782224f90a4db1ff3ea9730; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift index 15ca4a8c76..4efa212544 100644 --- a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift +++ b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift @@ -18,6 +18,7 @@ import Foundation import Common +import BrowserServicesKit final class SurveyURLBuilder { @@ -95,6 +96,23 @@ final class SurveyURLBuilder { return components.url } + func addPasswordsCountSurveyParameter(to originalURLString: String) -> URL? { + let surveyURLWithParameters = buildSurveyURL(from: originalURLString) + + guard let surveyURLWithParametersString = surveyURLWithParameters?.absoluteString, + var components = URLComponents(string: surveyURLWithParametersString), + let bucket = passwordsCountBucket() else { + return surveyURLWithParameters + } + + var queryItems = components.queryItems ?? [] + queryItems.append(URLQueryItem(name: "saved_passwords", value: bucket)) + + components.queryItems = queryItems + + return components.url + } + private func queryItem(parameter: SurveyURLParameters, value: String) -> URLQueryItem { let urlAllowed: CharacterSet = .alphanumerics.union(.init(charactersIn: "-._~")) let sanitizedValue = value.addingPercentEncoding(withAllowedCharacters: urlAllowed) @@ -105,4 +123,13 @@ final class SurveyURLBuilder { return URLQueryItem(name: parameter.rawValue, value: String(describing: value)) } + private func passwordsCountBucket() -> String? { + guard let secureVault = try? AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter.shared), + let bucket = try? secureVault.accountsCountBucket() else { + return nil + } + + return bucket + } + } diff --git a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift index c1ee576128..f48542c309 100644 --- a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift @@ -216,7 +216,7 @@ final class AutofillPreferencesModel: ObservableObject { daysSinceLastActive: activationDateStore.daysSinceLastActive() ) - guard let surveyUrl = surveyURLBuilder.buildSurveyURL(from: "https://selfserve.decipherinc.com/survey/selfserve/32ab/240307") else { + guard let surveyUrl = surveyURLBuilder.addPasswordsCountSurveyParameter(to: "https://selfserve.decipherinc.com/survey/selfserve/32ab/240307") else { return } From 6eea29e01b578dbdf779f7feec267098f438aa62 Mon Sep 17 00:00:00 2001 From: Anya Mallon Date: Wed, 8 May 2024 14:19:23 +0200 Subject: [PATCH 4/6] Renamed function --- DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift | 2 +- DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift index 4efa212544..a431276800 100644 --- a/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift +++ b/DuckDuckGo/Common/Surveys/SurveyURLBuilder.swift @@ -96,7 +96,7 @@ final class SurveyURLBuilder { return components.url } - func addPasswordsCountSurveyParameter(to originalURLString: String) -> URL? { + func buildSurveyURLWithPasswordsCountSurveyParameter(from originalURLString: String) -> URL? { let surveyURLWithParameters = buildSurveyURL(from: originalURLString) guard let surveyURLWithParametersString = surveyURLWithParameters?.absoluteString, diff --git a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift index f48542c309..e7e56043b8 100644 --- a/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/AutofillPreferencesModel.swift @@ -216,7 +216,7 @@ final class AutofillPreferencesModel: ObservableObject { daysSinceLastActive: activationDateStore.daysSinceLastActive() ) - guard let surveyUrl = surveyURLBuilder.addPasswordsCountSurveyParameter(to: "https://selfserve.decipherinc.com/survey/selfserve/32ab/240307") else { + guard let surveyUrl = surveyURLBuilder.buildSurveyURLWithPasswordsCountSurveyParameter(from: "https://selfserve.decipherinc.com/survey/selfserve/32ab/240307") else { return } From 8e89265f1d84994235a5e938819ef0090217f1b0 Mon Sep 17 00:00:00 2001 From: Anya Mallon Date: Wed, 8 May 2024 15:17:50 +0200 Subject: [PATCH 5/6] Updated mocks --- UnitTests/DataExport/MockSecureVault.swift | 12 ++++++++++++ .../Preferences/AutofillPreferencesModelTests.swift | 1 + 2 files changed, 13 insertions(+) diff --git a/UnitTests/DataExport/MockSecureVault.swift b/UnitTests/DataExport/MockSecureVault.swift index d9d55a92fa..5f07ac42f2 100644 --- a/UnitTests/DataExport/MockSecureVault.swift +++ b/UnitTests/DataExport/MockSecureVault.swift @@ -58,6 +58,14 @@ final class MockSecureVault: AutofillSecureVault { return storedAccounts } + func accountsCount() throws -> Int { + return storedAccounts.count + } + + func accountsCountBucket() throws -> String { + return "" + } + func accountsFor(domain: String) throws -> [SecureVaultModels.WebsiteAccount] { return storedAccounts.filter { $0.domain == domain } } @@ -327,6 +335,10 @@ class MockDatabaseProvider: AutofillDatabaseProvider { return _accounts } + func accountsCount() throws -> Int { + return _accounts.count + } + func neverPromptWebsites() throws -> [SecureVaultModels.NeverPromptWebsites] { return _neverPromptWebsites } diff --git a/UnitTests/Preferences/AutofillPreferencesModelTests.swift b/UnitTests/Preferences/AutofillPreferencesModelTests.swift index 9851f65f72..ef02dc27f1 100644 --- a/UnitTests/Preferences/AutofillPreferencesModelTests.swift +++ b/UnitTests/Preferences/AutofillPreferencesModelTests.swift @@ -29,6 +29,7 @@ final class AutofillPreferencesPersistorMock: AutofillPreferencesPersistor { var passwordManager: PasswordManager = .duckduckgo var autolockLocksFormFilling: Bool = false var debugScriptEnabled: Bool = false + var autofillSurveyEnabled: Bool = false } final class UserAuthenticatorMock: UserAuthenticating { From e75130977b8daab6e949420913cf34af2a7c3bb0 Mon Sep 17 00:00:00 2001 From: Anya Mallon Date: Thu, 9 May 2024 16:00:11 +0200 Subject: [PATCH 6/6] Set BSK release --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++-- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 3 ++- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 04720a2613..986e587e33 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12777,8 +12777,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = revision; - revision = 267927e389f3992ce782224f90a4db1ff3ea9730; + kind = exactVersion; + version = "144.0.1-1"; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 05a2fe5618..ec24112bc8 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,7 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "267927e389f3992ce782224f90a4db1ff3ea9730" + "revision" : "6ceabf1d257ff1d1164afb5b9139f9f20baf0c6e", + "version" : "144.0.1-1" } }, { diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 6b804973c4..525f21d8e1 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "144.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "144.0.1-1"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), ], diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 6aa9f1b8d2..8edcbdcd8a 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -31,7 +31,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "144.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "144.0.1-1"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.1"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 0d69eb5246..d9f3cfbaf7 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "144.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "144.0.1-1"), .package(path: "../SwiftUIExtensions") ], targets: [