diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 539b4cdd5c..0000000000
--- a/.eslintignore
+++ /dev/null
@@ -1 +0,0 @@
-submodules/
diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index 3b909f8fd4..0000000000
--- a/.eslintrc
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "env": {
- "browser": true,
- "es2017": true
- },
- "extends": [
- "standard"
- ],
- "globals": {
- "__firefox__": "readonly",
- "SECURITY_TOKEN": "readonly",
- "$FEATURE_SETTINGS$": "readonly",
- "$GPC_ENABLED$": "readonly",
- "$BLOCKING_ENABLED$": "readonly",
- "$TRACKER_DATA$": "readonly",
- "$IS_DEBUG$": "readonly",
- "webkit": "readonly"
- },
- "parserOptions": {
- "ecmaVersion": 7
- },
- "rules": {
- "indent": ["error", 4]
- }
-}
diff --git a/.maestro/onboarding_tests/01_control_group_onboarding.yaml b/.maestro/onboarding_tests/01_control_group_onboarding.yaml
deleted file mode 100644
index 609254b45a..0000000000
--- a/.maestro/onboarding_tests/01_control_group_onboarding.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-appId: com.duckduckgo.mobile.ios
-tags:
- - onboarding
-
----
-
-# Set up
-- runFlow:
- file: ../shared/setup.yaml
- env:
- ONBOARDING_COMPLETED: "false"
- APP_VARIANT: "ma"
-
-# Load Site
-- assertVisible:
- id: "searchEntry"
-- tapOn:
- id: "searchEntry"
-- inputText: "https://www.duckduckgo.com"
-- pressKey: Enter
-
-# Handle Onboarding
-- assertVisible: "Got It"
-- assertVisible: "Hide"
-- tapOn: "Got It"
-- assertVisible: "Close Tabs and Clear Data"
-- tapOn: "Close Tabs and Clear Data"
-- tapOn: "Close Tabs and Clear Data"
-- assertVisible: "You’ve got this!\n\nRemember: Every time you browse with me, a creepy ad loses its wings. 👍"
diff --git a/.maestro/onboarding_tests/03_experiment_group_linear_onboarding.yaml b/.maestro/onboarding_tests/01_onboarding_contextual.yaml
similarity index 97%
rename from .maestro/onboarding_tests/03_experiment_group_linear_onboarding.yaml
rename to .maestro/onboarding_tests/01_onboarding_contextual.yaml
index 5a86cdfd17..d5fe31dc01 100644
--- a/.maestro/onboarding_tests/03_experiment_group_linear_onboarding.yaml
+++ b/.maestro/onboarding_tests/01_onboarding_contextual.yaml
@@ -9,7 +9,7 @@ tags:
file: ../shared/setup.yaml
env:
ONBOARDING_COMPLETED: "false"
- APP_VARIANT: "mb"
+ APP_VARIANT: "mh"
# Handle Search Suggestions
- assertVisible: "Ready to get started?\nTry a search!"
@@ -50,3 +50,4 @@ tags:
- assertVisible: "You’ve got this!"
- assertVisible: "High five!"
- tapOn: "High five!"
+
diff --git a/.maestro/onboarding_tests/02_control_group_hide_onboarding.yaml b/.maestro/onboarding_tests/02_control_group_hide_onboarding.yaml
deleted file mode 100644
index a717b35c3a..0000000000
--- a/.maestro/onboarding_tests/02_control_group_hide_onboarding.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-appId: com.duckduckgo.mobile.ios
-tags:
- - onboarding
-
----
-
-# Set up
-- runFlow:
- file: ../shared/setup.yaml
- env:
- ONBOARDING_COMPLETED: "false"
- APP_VARIANT: "ma"
-
-# Load Site
-- assertVisible:
- id: "searchEntry"
-- tapOn:
- id: "searchEntry"
-- inputText: "https://www.duckduckgo.com"
-- pressKey: Enter
-
-# Handle Onboarding
-- assertVisible: "Got It"
-- assertVisible: "Hide"
-- tapOn: "Hide"
-- assertVisible: "Hide Tips Forever"
-- tapOn: "Hide Tips Forever"
-
-# Handle Fire Button
-- assertVisible: "Close Tabs and Clear Data"
-- tapOn: "Close Tabs and Clear Data"
-- tapOn: "Close Tabs and Clear Data"
-- assertNotVisible: "You’ve got this!\n\nRemember: Every time you browse with me, a creepy ad loses its wings. 👍"
diff --git a/.maestro/onboarding_tests/02_onboarding_add_to_dock_intro.yaml b/.maestro/onboarding_tests/02_onboarding_add_to_dock_intro.yaml
new file mode 100644
index 0000000000..8a354f5707
--- /dev/null
+++ b/.maestro/onboarding_tests/02_onboarding_add_to_dock_intro.yaml
@@ -0,0 +1,54 @@
+appId: com.duckduckgo.mobile.ios
+tags:
+ - onboarding
+
+---
+
+# Set up
+- runFlow:
+ file: ../shared/setup.yaml
+ env:
+ ONBOARDING_COMPLETED: "false"
+ APP_VARIANT: "mk"
+
+# Handle Search Suggestions
+- assertVisible: "Ready to get started?\nTry a search!"
+- assertVisible: "Surprise Me!"
+- tapOn: "Surprise Me!"
+
+# Handle First Dax Dialog
+- assertVisible: "That’s DuckDuckGo Search. Private. Fast. Fewer ads."
+- assertVisible: "Got It!"
+- tapOn: "Got It!"
+
+# Handle Site Suggestions
+- assertVisible: "Next, try visiting a site!"
+- assertVisible: "Surprise Me!"
+- tapOn: "Surprise Me!"
+
+# Handle Privacy Dashboard
+- assertVisible: "Got It!"
+- tapOn:
+ point: "6%,10%" # Shield icon.
+- assertVisible:
+ text: "View Tracker Companies"
+- assertVisible:
+ text: "Done"
+- tapOn: "Done"
+
+# Handle Fire Message
+- assertVisible: "Got It!"
+- tapOn: "Got It!"
+- assertVisible: "Instantly clear your browsing activity with the Fire Button.\n\nGive it a try! 🔥"
+
+# Handle Fire Button
+- assertVisible: "Close Tabs and Clear Data"
+- tapOn: "Close Tabs and Clear Data"
+- tapOn: "Close Tabs and Clear Data"
+
+# Handle End of Journey Dialog
+- assertVisible: "You’ve got this!"
+- assertVisible: "High five!"
+- tapOn: "High five!"
+
+
diff --git a/.maestro/onboarding_tests/03_onboarding_add_to_dock_contextual.yaml b/.maestro/onboarding_tests/03_onboarding_add_to_dock_contextual.yaml
new file mode 100644
index 0000000000..967ad2d11d
--- /dev/null
+++ b/.maestro/onboarding_tests/03_onboarding_add_to_dock_contextual.yaml
@@ -0,0 +1,54 @@
+appId: com.duckduckgo.mobile.ios
+tags:
+ - onboarding
+
+---
+
+# Set up
+- runFlow:
+ file: ../shared/setup.yaml
+ env:
+ ONBOARDING_COMPLETED: "false"
+ APP_VARIANT: "mo"
+
+# Handle Search Suggestions
+- assertVisible: "Ready to get started?\nTry a search!"
+- assertVisible: "Surprise Me!"
+- tapOn: "Surprise Me!"
+
+# Handle First Dax Dialog
+- assertVisible: "That’s DuckDuckGo Search. Private. Fast. Fewer ads."
+- assertVisible: "Got It!"
+- tapOn: "Got It!"
+
+# Handle Site Suggestions
+- assertVisible: "Next, try visiting a site!"
+- assertVisible: "Surprise Me!"
+- tapOn: "Surprise Me!"
+
+# Handle Privacy Dashboard
+- assertVisible: "Got It!"
+- tapOn:
+ point: "6%,10%" # Shield icon.
+- assertVisible:
+ text: "View Tracker Companies"
+- assertVisible:
+ text: "Done"
+- tapOn: "Done"
+
+# Handle Fire Message
+- assertVisible: "Got It!"
+- tapOn: "Got It!"
+- assertVisible: "Instantly clear your browsing activity with the Fire Button.\n\nGive it a try! 🔥"
+
+# Handle Fire Button
+- assertVisible: "Close Tabs and Clear Data"
+- tapOn: "Close Tabs and Clear Data"
+- tapOn: "Close Tabs and Clear Data"
+
+# Handle End of Journey Dialog
+- assertVisible: "Add me to your Dock!"
+- assertVisible: "Show Me How"
+- tapOn: "Start Browsing"
+
+
diff --git a/.maestro/release_tests/widgets.yaml b/.maestro/release_tests/widgets.yaml
index 491c82b079..0c3ba823c4 100644
--- a/.maestro/release_tests/widgets.yaml
+++ b/.maestro/release_tests/widgets.yaml
@@ -30,10 +30,14 @@ appId: com.duckduckgo.mobile.ios
# Validate search widget
- longPressOn:
point: 50%,50%
+
+# iOS 18 now has an edit button first
+- tapOn: "Edit"
- tapOn: "Add Widget"
- tapOn: "Search Widgets"
-- inputText: "DuckDuck"
-- tapOn: "DuckDuckGo"
+- inputText: "DuckDuckGo"
+- tapOn:
+ point: 30%,30%
- tapOn: " Add Widget"
- tapOn: "Done"
- tapOn: "DuckDuckGo"
@@ -48,10 +52,12 @@ appId: com.duckduckgo.mobile.ios
- pressKey: HOME
- longPressOn:
point: 50%,50%
+- tapOn: "Edit"
- tapOn: "Add Widget"
- tapOn: "Search Widgets"
-- inputText: "DuckDuck"
-- tapOn: "DuckDuckGo"
+- inputText: "DuckDuckGo"
+- tapOn:
+ point: 30%,30%
- assertVisible: "Search"
- swipe:
start: 90%, 50%
diff --git a/.maestro/shared/onboarding.yaml b/.maestro/shared/onboarding.yaml
index 2117d4205b..154954b742 100644
--- a/.maestro/shared/onboarding.yaml
+++ b/.maestro/shared/onboarding.yaml
@@ -10,17 +10,24 @@ appId: com.duckduckgo.mobile.ios
text: "Let’s Do It!"
index: 0
-# Disabled while UI testing is happening
-# - assertVisible: "Make DuckDuckGo your default browser."
+# Browser comparison chart
+# - assertVisible: "Protections activated!"
- tapOn:
text: "Skip"
+# Add To Dock Flow
- runFlow:
when:
- visible: "Which color looks best on me?"
+ visible: "Add me to your Dock!"
commands:
- - assertVisible: "Next"
- - tapOn: "Next"
- - assertVisible: "Where should I put your address bar?"
- - assertVisible: "Next"
- - tapOn: "Next"
+ - assertVisible: "Show Me How"
+ - tapOn: "Skip"
+
+# Customization Flow
+
+- assertVisible: "Which color looks best on me?"
+- assertVisible: "Next"
+- tapOn: "Next"
+- assertVisible: "Where should I put your address bar?"
+- assertVisible: "Next"
+- tapOn: "Next"
diff --git a/AutofillCredentialProvider/AutofillCredentialProvider.entitlements b/AutofillCredentialProvider/AutofillCredentialProvider.entitlements
new file mode 100644
index 0000000000..f7959669d1
--- /dev/null
+++ b/AutofillCredentialProvider/AutofillCredentialProvider.entitlements
@@ -0,0 +1,18 @@
+
+
+
+
+ com.apple.developer.authentication-services.autofill-credential-provider
+
+ com.apple.security.application-groups
+
+ $(GROUP_ID_PREFIX).vault
+ $(GROUP_ID_PREFIX).bookmarks
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)$(APP_ID)
+ $(AppIdentifierPrefix)$(VAULT_APP_GROUP)
+
+
+
diff --git a/AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements b/AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements
new file mode 100644
index 0000000000..f7959669d1
--- /dev/null
+++ b/AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements
@@ -0,0 +1,18 @@
+
+
+
+
+ com.apple.developer.authentication-services.autofill-credential-provider
+
+ com.apple.security.application-groups
+
+ $(GROUP_ID_PREFIX).vault
+ $(GROUP_ID_PREFIX).bookmarks
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)$(APP_ID)
+ $(AppIdentifierPrefix)$(VAULT_APP_GROUP)
+
+
+
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift
new file mode 100644
index 0000000000..fe36f9e3b2
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift
@@ -0,0 +1,74 @@
+//
+// CredentialProviderActivatedView.swift
+// DuckDuckGo
+//
+// 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 SwiftUI
+import DesignResourcesKit
+import DuckUI
+
+struct CredentialProviderActivatedView: View {
+
+ let viewModel: CredentialProviderActivatedViewModel
+ @State private var imageAppeared = false
+
+ var body: some View {
+ NavigationView {
+
+ VStack(spacing: 0) {
+
+ Image(.passwordsDDG96X96)
+ .padding(.top, 48)
+ .scaleEffect(imageAppeared ? 1 : 0.7)
+ .animation(
+ .interpolatingSpring(stiffness: 170, damping: 10)
+ .delay(0.1),
+ value: imageAppeared
+ )
+ .onAppear {
+ imageAppeared = true
+ }
+
+ Text(UserText.credentialProviderActivatedTitle)
+ .daxTitle2()
+ .foregroundColor(Color(designSystemColor: .textPrimary))
+ .padding(.top, 16)
+ .multilineTextAlignment(.center)
+
+ Spacer()
+
+ Button {
+ viewModel.launchDDGApp()
+ } label: {
+ Text(UserText.credentialProviderActivatedButton)
+ }
+ .buttonStyle(PrimaryButtonStyle())
+ .padding(.bottom, 12)
+
+ }
+ .padding(.horizontal, 24)
+ .navigationBarItems(trailing: Button(UserText.actionDone) {
+ viewModel.dismiss()
+ })
+ }
+ }
+
+}
+
+#Preview {
+ CredentialProviderActivatedView(viewModel: CredentialProviderActivatedViewModel())
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift
new file mode 100644
index 0000000000..bdf4eba455
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift
@@ -0,0 +1,42 @@
+//
+// CredentialProviderActivatedViewModel.swift
+// DuckDuckGo
+//
+// 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 Core
+
+struct CredentialProviderActivatedViewModel {
+
+ typealias LaunchAppCompletion = (_ shouldLaunchApp: Bool) -> Void
+
+ let completion: LaunchAppCompletion?
+
+ init(completion: LaunchAppCompletion? = nil) {
+ self.completion = completion
+ }
+
+ func dismiss() {
+ Pixel.fire(pixel: .autofillExtensionWelcomeDismiss)
+ completion?(false)
+ }
+
+ func launchDDGApp() {
+ Pixel.fire(pixel: .autofillExtensionWelcomeLaunchApp)
+ completion?(true)
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift
new file mode 100644
index 0000000000..a0701ec178
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift
@@ -0,0 +1,151 @@
+//
+// CredentialProviderListItemTableViewCell.swift
+// DuckDuckGo
+//
+// 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 UIKit
+import Core
+import DesignResourcesKit
+
+class CredentialProviderListItemTableViewCell: UITableViewCell {
+
+ static var reuseIdentifier = "CredentialProviderListItemTableViewCell"
+
+ var disclosureButtonTapped: (() -> Void)?
+
+ private lazy var titleLabel: UILabel = {
+ let label = UILabel(frame: CGRect.zero)
+ label.font = .preferredFont(forTextStyle: .callout)
+ label.textColor = .init(designSystemColor: .textPrimary)
+ label.lineBreakMode = .byTruncatingMiddle
+ return label
+ }()
+
+ private lazy var subtitleLabel: UILabel = {
+ let label = UILabel(frame: CGRect.zero)
+ label.font = .preferredFont(forTextStyle: .footnote)
+ label.textColor = .init(designSystemColor: .textPrimary)
+ label.lineBreakMode = .byTruncatingMiddle
+ return label
+ }()
+
+ private lazy var iconImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.contentMode = .scaleAspectFill
+ imageView.layer.masksToBounds = true
+ imageView.layer.cornerRadius = 4
+ return imageView
+ }()
+
+ private lazy var textStackView: UIStackView = {
+ let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
+ stackView.axis = .vertical
+ stackView.distribution = .fill
+ stackView.spacing = 3
+ return stackView
+ }()
+
+ private lazy var contentStackView: UIStackView = {
+ let stackView = UIStackView(arrangedSubviews: [iconImageView, textStackView])
+ stackView.axis = .horizontal
+ stackView.spacing = 12
+ stackView.alignment = .center
+ return stackView
+ }()
+
+ private lazy var disclosureButton: UIButton = {
+ let button = UIButton(type: .system)
+ let image = UIImage(systemName: "chevron.forward")
+ let boldImage = image?.withConfiguration(UIImage.SymbolConfiguration(pointSize: 11, weight: .bold))
+ button.setImage(boldImage, for: .normal)
+ button.tintColor = UIColor.tertiaryLabel
+ button.addTarget(self, action: #selector(handleDisclosureButtonTap), for: .touchUpInside)
+
+ let buttonSize: CGFloat = 44
+ button.frame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
+ button.contentHorizontalAlignment = .center
+ button.contentVerticalAlignment = .center
+
+ return button
+ }()
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ installSubviews()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ var item: AutofillLoginItem? {
+ didSet {
+ guard let item = item else {
+ return
+ }
+ setupContentView(with: item)
+ }
+ }
+
+ private func installSubviews() {
+ contentView.addSubview(contentStackView)
+ contentView.addSubview(disclosureButton)
+ installConstraints()
+ }
+
+ private func installConstraints() {
+ contentStackView.translatesAutoresizingMaskIntoConstraints = false
+ iconImageView.translatesAutoresizingMaskIntoConstraints = false
+ disclosureButton.translatesAutoresizingMaskIntoConstraints = false
+
+ let imageSize: CGFloat = 32
+ let margins = contentView.layoutMarginsGuide
+
+ NSLayoutConstraint.activate([
+ iconImageView.widthAnchor.constraint(equalToConstant: imageSize),
+ iconImageView.heightAnchor.constraint(equalToConstant: imageSize),
+
+ disclosureButton.widthAnchor.constraint(equalToConstant: 44),
+ disclosureButton.heightAnchor.constraint(equalToConstant: 44),
+ disclosureButton.centerYAnchor.constraint(equalTo: margins.centerYAnchor),
+ disclosureButton.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: 16),
+
+ contentStackView.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
+ contentStackView.trailingAnchor.constraint(equalTo: disclosureButton.leadingAnchor, constant: -12),
+ contentStackView.topAnchor.constraint(equalTo: margins.topAnchor),
+ contentStackView.bottomAnchor.constraint(equalTo: margins.bottomAnchor)
+ ])
+ }
+
+ private func setupContentView(with item: AutofillLoginItem) {
+ titleLabel.text = item.title
+ subtitleLabel.text = item.subtitle
+ iconImageView.image = FaviconHelper.loadImageFromCache(forDomain: item.account.domain, preferredFakeFaviconLetters: item.preferredFaviconLetters)
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ contentStackView.frame = contentView.bounds
+
+ separatorInset = UIEdgeInsets(top: 0, left: contentView.layoutMargins.left + textStackView.frame.origin.x, bottom: 0, right: 0)
+ }
+
+ @objc private func handleDisclosureButtonTap() {
+ disclosureButtonTapped?()
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift
new file mode 100644
index 0000000000..6094773e0c
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift
@@ -0,0 +1,430 @@
+//
+// CredentialProviderListViewController.swift
+// DuckDuckGo
+//
+// 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 UIKit
+import AuthenticationServices
+import BrowserServicesKit
+import Combine
+import Common
+import Core
+import SwiftUI
+
+final class CredentialProviderListViewController: UIViewController {
+
+ private let viewModel: CredentialProviderListViewModel
+ private let shouldProvideTextToInsert: Bool
+ private let tld: TLD
+ private let onRowSelected: (AutofillLoginItem) -> Void
+ private let onTextProvided: (String) -> Void
+ private let onDismiss: () -> Void
+ private var cancellables: Set = []
+
+ private lazy var tableView: UITableView = {
+ let tableView = UITableView(frame: .zero, style: .insetGrouped)
+ tableView.delegate = self
+ tableView.dataSource = self
+ tableView.estimatedRowHeight = 60
+ tableView.register(CredentialProviderListItemTableViewCell.self, forCellReuseIdentifier: CredentialProviderListItemTableViewCell.reuseIdentifier)
+ return tableView
+ }()
+
+ private lazy var searchController: UISearchController = {
+ let searchController = UISearchController(searchResultsController: nil)
+ searchController.searchResultsUpdater = self
+ searchController.searchBar.delegate = self
+ searchController.obscuresBackgroundDuringPresentation = false
+ searchController.searchBar.placeholder = UserText.credentialProviderListSearchPlaceholder
+ navigationItem.hidesSearchBarWhenScrolling = false
+ definesPresentationContext = true
+
+ return searchController
+ }()
+
+ private lazy var lockedView = { [weak self] in
+ let view = LockScreenView()
+ let hostingController = UIHostingController(rootView: view)
+ self?.installChildViewController(hostingController)
+ return hostingController.view ?? UIView()
+ }()
+
+ private let emptySearchView = EmptySearchView()
+
+ private lazy var emptyView: UIView = { [weak self] in
+ let emptyView = EmptyView()
+
+ let hostingController = UIHostingController(rootView: emptyView)
+ self?.installChildViewController(hostingController)
+ hostingController.view.backgroundColor = .clear
+ return hostingController.view
+ }()
+
+ private lazy var emptySearchViewCenterYConstraint: NSLayoutConstraint = {
+ NSLayoutConstraint(item: emptySearchView,
+ attribute: .centerY,
+ relatedBy: .equal,
+ toItem: tableView,
+ attribute: .top,
+ multiplier: 1,
+ constant: (tableView.frame.height / 2))
+ }()
+
+ init(serviceIdentifiers: [ASCredentialServiceIdentifier],
+ secureVault: (any AutofillSecureVault)?,
+ credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging,
+ shouldProvideTextToInsert: Bool,
+ tld: TLD,
+ onRowSelected: @escaping (AutofillLoginItem) -> Void,
+ onTextProvided: @escaping (String) -> Void,
+ onDismiss: @escaping () -> Void) {
+ self.viewModel = CredentialProviderListViewModel(serviceIdentifiers: serviceIdentifiers,
+ secureVault: secureVault,
+ credentialIdentityStoreManager: credentialIdentityStoreManager,
+ tld: tld)
+ self.shouldProvideTextToInsert = shouldProvideTextToInsert
+ self.tld = tld
+ self.onRowSelected = onRowSelected
+ self.onTextProvided = onTextProvided
+ self.onDismiss = onDismiss
+
+ super.init(nibName: nil, bundle: nil)
+
+ if #available(iOS 18.0, *) {
+ authenticate()
+ } else {
+ // pre-iOS 18.0 authentication can fail silently if extension is loaded twice in quick succession
+ // if authenticate is called without a slight delay
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in
+ self?.authenticate()
+ }
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = UserText.credentialProviderListTitle
+
+ if let itemPrompt = viewModel.serviceIdentifierPromptLabel {
+ navigationItem.prompt = itemPrompt
+ }
+
+ let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped))
+ navigationItem.rightBarButtonItem = doneItem
+
+ setupCancellables()
+ installSubviews()
+ installConstraints()
+ decorate()
+ updateViewState()
+ registerForKeyboardNotifications()
+
+ navigationItem.searchController = searchController
+
+ Pixel.fire(pixel: .autofillExtensionPasswordsOpened)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ viewModel.authenticateInvalidateContext()
+ }
+
+ override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
+ super.viewWillTransition(to: size, with: coordinator)
+
+ coordinator.animate(alongsideTransition: { _ in
+ if !self.searchController.isActive {
+ self.navigationItem.searchController = nil
+ }
+ }, completion: { _ in
+ self.updateSearchController()
+ })
+ }
+
+ private func decorate() {
+ view.backgroundColor = UIColor(designSystemColor: .background)
+ tableView.backgroundColor = UIColor(designSystemColor: .background)
+ tableView.separatorColor = UIColor(designSystemColor: .lines)
+ tableView.sectionIndexColor = UIColor(designSystemColor: .accent)
+
+ navigationController?.navigationBar.barTintColor = UIColor(designSystemColor: .panel)
+ navigationController?.navigationBar.tintColor = UIColor(designSystemColor: .textPrimary)
+
+ let appearance = UINavigationBarAppearance()
+ appearance.shadowColor = .clear
+ appearance.backgroundColor = UIColor(designSystemColor: .background)
+
+ navigationController?.navigationBar.standardAppearance = appearance
+ navigationController?.navigationBar.scrollEdgeAppearance = appearance
+
+ tableView.reloadData()
+ }
+
+ private func authenticate() {
+ viewModel.authenticate {[weak self] error in
+ guard let self = self else { return }
+
+ if error != nil {
+ if error != .noAuthAvailable {
+ self.onDismiss()
+ } else {
+ let alert = UIAlertController.makeDeviceAuthenticationAlert { [weak self] in
+ self?.onDismiss()
+ }
+ present(alert, animated: true)
+ }
+ }
+ }
+ }
+
+ private func installSubviews() {
+ view.addSubview(tableView)
+ tableView.addSubview(emptySearchView)
+ view.addSubview(lockedView)
+ }
+
+ private func installConstraints() {
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ emptySearchView.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+ tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
+ tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
+ tableView.topAnchor.constraint(equalTo: view.topAnchor),
+ tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
+ emptySearchView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
+ emptySearchViewCenterYConstraint,
+ emptySearchView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
+ emptySearchView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
+ ])
+ }
+
+ private func setupCancellables() {
+ viewModel.$viewState
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] _ in
+ self?.updateViewState()
+ }
+ .store(in: &cancellables)
+
+ viewModel.$sections
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] _ in
+ self?.tableView.reloadData()
+ }
+ .store(in: &cancellables)
+ }
+
+ private func updateViewState() {
+
+ switch viewModel.viewState {
+ case .showItems:
+ tableView.isHidden = false
+ lockedView.isHidden = true
+ emptySearchView.isHidden = true
+ emptyView.isHidden = true
+ case .noAuthAvailable:
+ tableView.isHidden = true
+ lockedView.isHidden = true
+ emptySearchView.isHidden = true
+ emptyView.isHidden = true
+ case .authLocked:
+ tableView.isHidden = true
+ lockedView.isHidden = false
+ emptySearchView.isHidden = true
+ emptyView.isHidden = true
+ case .empty:
+ tableView.isHidden = true
+ lockedView.isHidden = true
+ emptySearchView.isHidden = true
+ emptyView.isHidden = false
+ case .searching:
+ tableView.isHidden = false
+ lockedView.isHidden = true
+ emptySearchView.isHidden = true
+ emptyView.isHidden = true
+ case .searchingNoResults:
+ tableView.isHidden = false
+ lockedView.isHidden = true
+ emptySearchView.isHidden = false
+ emptyView.isHidden = true
+ }
+ updateSearchController()
+ tableView.reloadData()
+ }
+
+ private func updateSearchController() {
+ switch viewModel.viewState {
+ case .showItems:
+ if tableView.isEditing {
+ navigationItem.searchController = nil
+ } else {
+ navigationItem.searchController = searchController
+ }
+ case .searching, .searchingNoResults:
+ navigationItem.searchController = searchController
+ case .authLocked:
+ navigationItem.searchController = viewModel.hasAccountsSaved ? searchController : nil
+ case .empty, .noAuthAvailable:
+ navigationItem.searchController = nil
+ }
+ }
+
+ @objc private func doneTapped() {
+ onDismiss()
+ Pixel.fire(pixel: .autofillExtensionPasswordsDismissed)
+ }
+
+}
+
+extension CredentialProviderListViewController: UITableViewDataSource {
+
+ func numberOfSections(in tableView: UITableView) -> Int {
+ viewModel.sections.count
+ }
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ viewModel.rowsInSection(section)
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ switch viewModel.sections[indexPath.section] {
+ case .suggestions(_, items: let items), .credentials(_, let items):
+ guard let cell = tableView.dequeueReusableCell(withIdentifier: CredentialProviderListItemTableViewCell.reuseIdentifier,
+ for: indexPath) as? CredentialProviderListItemTableViewCell else {
+ fatalError("Could not dequeue cell")
+ }
+ cell.item = items[indexPath.row]
+ cell.backgroundColor = UIColor(designSystemColor: .surface)
+
+ cell.disclosureButtonTapped = { [weak self] in
+ let item = items[indexPath.row]
+ self?.presentDetailsForCredentials(item: item)
+ }
+ return cell
+ default:
+ return UITableViewCell()
+ }
+ }
+
+ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+ switch viewModel.sections[section] {
+ case .suggestions(let title, _), .credentials(let title, _):
+ return title
+ default:
+ return nil
+ }
+ }
+
+ func sectionIndexTitles(for tableView: UITableView) -> [String]? {
+ viewModel.viewState == .showItems ? UILocalizedIndexedCollation.current().sectionIndexTitles : []
+ }
+
+ private func presentDetailsForCredentials(item: AutofillLoginItem) {
+ let detailViewController = CredentialProviderListDetailsViewController(account: item.account,
+ tld: tld,
+ shouldProvideTextToInsert: self.shouldProvideTextToInsert)
+ detailViewController.delegate = self
+
+ self.navigationController?.pushViewController(detailViewController, animated: true)
+ }
+}
+
+extension CredentialProviderListViewController: UITableViewDelegate {
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ tableView.deselectRow(at: indexPath, animated: true)
+ switch viewModel.sections[indexPath.section] {
+ case .suggestions(_, items: let items), .credentials(_, let items):
+ let item = items[indexPath.row]
+ if shouldProvideTextToInsert {
+ presentDetailsForCredentials(item: item)
+ } else {
+ onRowSelected(item)
+ Pixel.fire(pixel: .autofillExtensionPasswordSelected)
+ }
+ default:
+ return
+ }
+ }
+
+}
+
+extension CredentialProviderListViewController: UISearchResultsUpdating {
+
+ func updateSearchResults(for searchController: UISearchController) {
+ viewModel.isSearching = searchController.isActive
+
+ if let query = searchController.searchBar.text {
+ viewModel.filterData(with: query)
+ emptySearchView.query = query
+ tableView.reloadData()
+ }
+ }
+
+}
+
+extension CredentialProviderListViewController: UISearchBarDelegate {
+
+ func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
+ viewModel.isSearching = false
+
+ viewModel.filterData(with: "")
+ tableView.reloadData()
+ }
+
+}
+
+// MARK: Keyboard
+
+extension CredentialProviderListViewController {
+
+ private func registerForKeyboardNotifications() {
+ NotificationCenter.default.addObserver(self,
+ selector: #selector(adjustForKeyboard),
+ name: UIResponder.keyboardWillChangeFrameNotification,
+ object: nil)
+ }
+
+ @objc private func adjustForKeyboard(notification: NSNotification) {
+ guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
+ return
+ }
+
+ let keyboardScreenEndFrame = keyboardValue.cgRectValue
+ let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
+
+ emptySearchViewCenterYConstraint.constant = min(
+ (keyboardViewEndFrame.minY + emptySearchView.frame.height) / 2 - searchController.searchBar.frame.height,
+ (tableView.frame.height / 2) - searchController.searchBar.frame.height
+ )
+ }
+}
+
+extension CredentialProviderListViewController: CredentialProviderListDetailsViewControllerDelegate {
+
+ func credentialProviderListDetailsViewControllerDidProvideText(_ controller: CredentialProviderListDetailsViewController, text: String) {
+ onTextProvided(text)
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift
new file mode 100644
index 0000000000..1b0ab0c601
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift
@@ -0,0 +1,253 @@
+//
+// CredentialProviderListViewModel.swift
+// DuckDuckGo
+//
+// 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 AuthenticationServices
+import BrowserServicesKit
+import Combine
+import Common
+import Core
+import os.log
+
+final class CredentialProviderListViewModel: ObservableObject {
+
+ enum ViewState {
+ case authLocked
+ case noAuthAvailable
+ case empty
+ case showItems
+ case searching
+ case searchingNoResults
+ }
+
+ var isSearching: Bool = false {
+ didSet {
+ if oldValue != isSearching, isSearching {
+ Pixel.fire(pixel: .autofillExtensionPasswordsSearch)
+ }
+ }
+ }
+ var authenticationNotRequired = false
+
+ private let serviceIdentifiers: [ASCredentialServiceIdentifier]
+ private let secureVault: (any AutofillSecureVault)?
+ private let credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging
+ private var accounts = [SecureVaultModels.WebsiteAccount]()
+ private var accountsToSuggest = [SecureVaultModels.WebsiteAccount]()
+ private var cancellables: Set = []
+ private let tld: TLD
+ private let autofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher()
+ private let autofillDomainNameUrlSort = AutofillDomainNameUrlSort()
+
+ let authenticator = UserAuthenticator(reason: UserText.credentialProviderListAuthenticationReason,
+ cancelTitle: UserText.credentialProviderListAuthenticationCancelButton)
+ var hasAccountsSaved: Bool {
+ return !accounts.isEmpty
+ }
+
+ var serviceIdentifierPromptLabel: String? {
+ guard let identifier = serviceIdentifiers.first?.identifier else {
+ return nil
+ }
+ return String(format: UserText.credentialProviderListPrompt, autofillDomainNameUrlMatcher.normalizeUrlForWeb(identifier))
+ }
+
+ @Published private(set) var viewState: CredentialProviderListViewModel.ViewState = .authLocked
+ @Published private(set) var sections = [AutofillLoginListSectionType]() {
+ didSet {
+ updateViewState()
+ }
+ }
+
+ init(serviceIdentifiers: [ASCredentialServiceIdentifier],
+ secureVault: (any AutofillSecureVault)?,
+ credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging,
+ tld: TLD) {
+ self.serviceIdentifiers = serviceIdentifiers
+ self.secureVault = secureVault
+ self.credentialIdentityStoreManager = credentialIdentityStoreManager
+ self.tld = tld
+
+ if let count = getAccountsCount() {
+ authenticationNotRequired = count == 0
+ }
+ updateData()
+ setupCancellables()
+ }
+
+ private func getAccountsCount() -> Int? {
+ guard let secureVault = secureVault else {
+ return nil
+ }
+
+ do {
+ return try secureVault.accountsCount()
+ } catch {
+ return nil
+ }
+ }
+
+ private func fetchAccounts() -> [SecureVaultModels.WebsiteAccount] {
+ guard let secureVault = secureVault else {
+ return []
+ }
+
+ do {
+ let allAccounts = try secureVault.accounts()
+ return allAccounts
+ } catch {
+ Logger.autofill.error("Failed to fetch accounts \(error.localizedDescription, privacy: .public)")
+ return []
+ }
+ }
+
+ func updateData() {
+ self.accounts = fetchAccounts()
+ self.accountsToSuggest = fetchSuggestedAccounts()
+ self.sections = makeSections(with: accounts)
+
+ Task {
+ await credentialIdentityStoreManager.replaceCredentialStore(with: accounts)
+ }
+ }
+
+ private func setupCancellables() {
+ authenticator.$state
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] _ in
+ self?.updateViewState()
+ }
+ .store(in: &cancellables)
+ }
+
+ func authenticate(completion: @escaping (UserAuthenticator.AuthError?) -> Void) {
+ if !authenticator.canAuthenticate() {
+ viewState = .noAuthAvailable
+ completion(.noAuthAvailable)
+ return
+ }
+
+ if viewState != .authLocked {
+ completion(nil)
+ return
+ }
+
+ authenticator.authenticate(completion: completion)
+ }
+
+ func authenticateInvalidateContext() {
+ authenticator.invalidateContext()
+ }
+
+ private func fetchSuggestedAccounts() -> [SecureVaultModels.WebsiteAccount] {
+
+ var suggestedAccounts = [SecureVaultModels.WebsiteAccount]()
+
+ serviceIdentifiers.compactMap { URL(string: $0.identifier) }.forEach { url in
+ suggestedAccounts += accounts.filter { account in
+ return autofillDomainNameUrlMatcher.isMatchingForAutofill(
+ currentSite: url.absoluteString,
+ savedSite: account.domain ?? "",
+ tld: tld
+ )
+ }
+ }
+
+ let sortedSuggestions = suggestedAccounts.sorted(by: {
+ autofillDomainNameUrlSort.compareAccountsForSortingAutofill(lhs: $0, rhs: $1, tld: tld) == .orderedAscending
+ })
+
+ return sortedSuggestions
+ }
+
+ func filterData(with query: String? = nil) {
+ var filteredAccounts = self.accounts
+
+ if let query = query, query.count > 0 {
+ filteredAccounts = filteredAccounts.filter { account in
+ if !account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher).lowercased().contains(query.lowercased()) &&
+ !(account.domain ?? "").lowercased().contains(query.lowercased()) &&
+ !(account.username ?? "").lowercased().contains(query.lowercased()) {
+ return false
+ }
+ return true
+ }
+ }
+ self.sections = makeSections(with: filteredAccounts)
+ }
+
+ func rowsInSection(_ section: Int) -> Int {
+ switch self.sections[section] {
+ case .suggestions(_, let items):
+ return items.count
+ case .credentials(_, let items):
+ return items.count
+ default:
+ return 0
+ }
+ }
+
+ private func makeSections(with accounts: [SecureVaultModels.WebsiteAccount]) -> [AutofillLoginListSectionType] {
+ var newSections = [AutofillLoginListSectionType]()
+
+ if !isSearching && !accountsToSuggest.isEmpty {
+ let accountItems = accountsToSuggest.map { AutofillLoginItem(account: $0,
+ tld: tld,
+ autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher,
+ autofillDomainNameUrlSort: autofillDomainNameUrlSort)
+ }
+ newSections.append(.suggestions(title: UserText.credentialProviderListSuggested, items: accountItems))
+ }
+
+ let viewModelsGroupedByFirstLetter = accounts.groupedByFirstLetter(
+ tld: tld,
+ autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher,
+ autofillDomainNameUrlSort: autofillDomainNameUrlSort)
+ let accountSections = viewModelsGroupedByFirstLetter.sortedIntoSections(autofillDomainNameUrlSort,
+ tld: tld)
+
+ newSections.append(contentsOf: accountSections)
+ return newSections
+ }
+
+ private func updateViewState() {
+ var newViewState: CredentialProviderListViewModel.ViewState
+
+ if authenticator.state == .loggedOut && !authenticationNotRequired {
+ newViewState = .authLocked
+ } else if authenticator.state == .notAvailable {
+ newViewState = .noAuthAvailable
+ } else if isSearching {
+ if sections.count == 0 {
+ newViewState = .searchingNoResults
+ } else {
+ newViewState = .searching
+ }
+ } else {
+ newViewState = sections.count > 0 ? .showItems : .empty
+ }
+
+
+ // Avoid unnecessary updates
+ if newViewState != viewState {
+ viewState = newViewState
+ }
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptySearchView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptySearchView.swift
new file mode 100644
index 0000000000..b61c976612
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptySearchView.swift
@@ -0,0 +1,92 @@
+//
+// EmptySearchView.swift
+// DuckDuckGo
+//
+// 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 UIKit
+import DesignResourcesKit
+
+final class EmptySearchView: UIView {
+
+ private lazy var title: UILabel = {
+ let label = UILabel(frame: CGRect.zero)
+
+ label.font = .systemFont(ofSize: UIFont.preferredFont(forTextStyle: .title2).pointSize * 1.091, weight: .regular)
+ label.text = UserText.credentialProviderListSearchNoResultTitle
+ label.numberOfLines = 0
+ label.textAlignment = .center
+ label.lineBreakMode = .byWordWrapping
+ label.textColor = UIColor(designSystemColor: .textPrimary)
+ return label
+ }()
+
+ private lazy var subtitle: UILabel = {
+ let label = UILabel(frame: CGRect.zero)
+
+ label.font = .preferredFont(forTextStyle: .callout)
+ label.text = ""
+ label.numberOfLines = 0
+ label.textAlignment = .center
+ label.lineBreakMode = .byWordWrapping
+ label.textColor = UIColor(designSystemColor: .textPrimary)
+
+ return label
+ }()
+
+ private lazy var stackContentView: UIStackView = {
+ let stackView = UIStackView(arrangedSubviews: [title, subtitle])
+ stackView.axis = .vertical
+ stackView.spacing = 3
+ return stackView
+ }()
+
+ var query: String = "" {
+ didSet {
+ if query.count > 0 {
+ subtitle.text = UserText.credentialProviderListSearchNoResultSubtitle(for: query)
+ } else {
+ subtitle.text = ""
+ }
+ }
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ installSubviews()
+ installConstraints()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func installSubviews() {
+ addSubview(stackContentView)
+ }
+
+ private func installConstraints() {
+ stackContentView.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+ stackContentView.centerXAnchor.constraint(equalTo: centerXAnchor),
+ stackContentView.centerYAnchor.constraint(equalTo: centerYAnchor),
+ heightAnchor.constraint(equalTo: stackContentView.heightAnchor),
+ widthAnchor.constraint(equalTo: stackContentView.widthAnchor)
+ ])
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptyView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptyView.swift
new file mode 100644
index 0000000000..7cce2d7329
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptyView.swift
@@ -0,0 +1,55 @@
+//
+// EmptyView.swift
+// DuckDuckGo
+//
+// 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 SwiftUI
+import DesignResourcesKit
+
+struct EmptyView: View {
+
+ var body: some View {
+
+ VStack(spacing: 0) {
+
+ Image(.passwordsAdd96X96)
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(width: 96, height: 96)
+
+ Text(UserText.credentialProviderListEmptyViewTitle)
+ .daxTitle3()
+ .foregroundColor(Color(designSystemColor: .textPrimary))
+ .padding(.top, 16)
+ .multilineTextAlignment(.center)
+ .lineLimit(nil)
+
+ Text(UserText.credentialProviderListEmptyViewSubtitle)
+ .daxBodyRegular()
+ .foregroundColor(Color.init(designSystemColor: .textSecondary))
+ .multilineTextAlignment(.center)
+ .padding(.top, 8)
+ .lineLimit(nil)
+
+ }
+ .frame(maxWidth: 300.0)
+ .padding(.bottom, 60)
+ }}
+
+#Preview {
+ EmptyView()
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsView.swift
new file mode 100644
index 0000000000..ae2a9113ce
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsView.swift
@@ -0,0 +1,359 @@
+//
+// CredentialProviderListDetailsView.swift
+// DuckDuckGo
+//
+// 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 SwiftUI
+import DuckUI
+import DesignResourcesKit
+
+struct CredentialProviderListDetailsView: View {
+
+ @ObservedObject var viewModel: CredentialProviderListDetailsViewModel
+
+ var body: some View {
+ listWithBackground
+ }
+
+ @ViewBuilder
+ private var listWithBackground: some View {
+ if #available(iOS 16.0, *) {
+ list
+ .scrollContentBackground(.hidden)
+ .background(Color(designSystemColor: .background))
+ } else {
+ list
+ .background(Color(designSystemColor: .background))
+ }
+ }
+
+ private var list: some View {
+ List {
+ viewingContentView
+ }
+ .simultaneousGesture(
+ DragGesture().onChanged({_ in
+ viewModel.selectedCell = nil
+ }))
+ .listStyle(.insetGrouped)
+ }
+
+ private var viewingContentView: some View {
+ Group {
+ Section {
+ Header(viewModel: viewModel.headerViewModel)
+ }
+
+ Section {
+ usernameCell()
+ .onTapGesture {
+ if viewModel.shouldProvideTextToInsert {
+ viewModel.textToReturn(.username)
+ }
+ }
+
+ passwordCell()
+ .onTapGesture {
+ if viewModel.shouldProvideTextToInsert {
+ viewModel.textToReturn(.password)
+ }
+ }
+ }
+
+ Section {
+ addressCell()
+ }
+
+ Section {
+ notesCell()
+ }
+ }
+ }
+
+ private func usernameCell() -> some View {
+ CopyableCell(title: UserText.credentialProviderDetailsUsername,
+ subtitle: viewModel.usernameDisplayString,
+ selectedCell: $viewModel.selectedCell,
+ buttonImageName: "Copy-24",
+ buttonAccessibilityLabel: UserText.credentialProviderDetailsCopyPrompt(for: UserText.credentialProviderDetailsUsername),
+ buttonAction: { viewModel.copyToPasteboard(.username) })
+ }
+
+ private func passwordCell() -> some View {
+ CopyableCell(title: UserText.credentialProviderDetailsPassword,
+ subtitle: viewModel.userVisiblePassword,
+ selectedCell: $viewModel.selectedCell,
+ isMonospaced: true,
+ buttonImageName: viewModel.isPasswordHidden ? "Eye-24" : "Eye-Closed-24",
+ buttonAccessibilityLabel: viewModel.isPasswordHidden ? UserText.credentialProviderDetailsShowPassword : UserText.credentialProviderDetailsHidePassword,
+ buttonAction: { viewModel.isPasswordHidden.toggle() },
+ secondaryButtonImageName: "Copy-24",
+ secondaryButtonAccessibilityLabel: UserText.credentialProviderDetailsCopyPrompt(for: UserText.credentialProviderDetailsPassword),
+ secondaryButtonAction: { viewModel.copyToPasteboard(.password) })
+ }
+
+ private func addressCell() -> some View {
+ CopyableCell(title: UserText.credentialProviderDetailsAddress,
+ subtitle: viewModel.address,
+ selectedCell: $viewModel.selectedCell,
+ truncationMode: .middle,
+ buttonImageName: "Copy-24",
+ buttonAccessibilityLabel: UserText.credentialProviderDetailsCopyPrompt(for: UserText.credentialProviderDetailsAddress),
+ buttonAction: { viewModel.copyToPasteboard(.address) })
+ }
+
+ private func notesCell() -> some View {
+ CopyableCell(title: UserText.credentialProviderDetailsNotes,
+ subtitle: viewModel.notes,
+ selectedCell: $viewModel.selectedCell,
+ truncationMode: .middle,
+ multiLine: true)
+ }
+
+}
+
+private struct Header: View {
+
+ @Environment(\.colorScheme) private var colorScheme
+
+ private struct Constants {
+ static let imageSize: CGFloat = 32
+ static let horizontalStackSpacing: CGFloat = 12
+ static let verticalStackSpacing: CGFloat = 1
+ static let viewHeight: CGFloat = 60
+ static let insets = EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
+ }
+
+ var viewModel: CredentialProviderListDetailsHeaderViewModel
+
+ var body: some View {
+ HStack(spacing: Constants.horizontalStackSpacing) {
+ Image(uiImage: viewModel.favicon)
+ .resizable()
+ .cornerRadius(4)
+ .scaledToFit()
+ .frame(width: Constants.imageSize, height: Constants.imageSize)
+ .accessibilityHidden(true)
+
+ VStack(alignment: .leading, spacing: Constants.verticalStackSpacing) {
+ Text(viewModel.title)
+ .font(.callout)
+ .foregroundColor(colorScheme == .light ? .gray90 : .white)
+ .truncationMode(.middle)
+ .lineLimit(1)
+
+ Text(viewModel.subtitle)
+ .font(.footnote)
+ .foregroundColor(colorScheme == .light ? .gray50 : .gray20)
+ }
+
+ Spacer()
+ }
+ .frame(minHeight: Constants.viewHeight)
+ .frame(maxWidth: .infinity)
+ .contentShape(Rectangle())
+ .listRowBackground(Color(designSystemColor: .surface))
+ .listRowInsets(Constants.insets)
+ }
+}
+
+private struct CopyableCell: View {
+ @State private var id = UUID()
+ let title: String
+ let subtitle: String
+ @Binding var selectedCell: UUID?
+ var truncationMode: Text.TruncationMode = .tail
+ var multiLine: Bool = false
+ var isMonospaced: Bool = false
+
+ var buttonImageName: String?
+ var buttonAccessibilityLabel: String?
+ var buttonAction: (() -> Void)?
+
+ var secondaryButtonImageName: String?
+ var secondaryButtonAccessibilityLabel: String?
+ var secondaryButtonAction: (() -> Void)?
+
+ var shouldProvideTextToInsertAction: (() -> Void)?
+
+ var body: some View {
+ ZStack {
+ HStack {
+ VStack(alignment: .leading, spacing: Constants.verticalPadding) {
+ Text(title)
+ .label4Style()
+ HStack {
+ if multiLine {
+ Text(subtitle)
+ .label4Style(design: isMonospaced ? .monospaced : .default,
+ foregroundColorLight: ForegroundColor(isSelected: selectedCell == id).color,
+ foregroundColorDark: .gray30)
+ .truncationMode(truncationMode)
+ .frame(maxHeight: .greatestFiniteMagnitude)
+ .textSelection(.enabled)
+ } else {
+ Text(subtitle)
+ .label4Style(design: isMonospaced ? .monospaced : .default,
+ foregroundColorLight: ForegroundColor(isSelected: selectedCell == id).color,
+ foregroundColorDark: .gray30)
+ .truncationMode(truncationMode)
+ }
+ }
+ }
+ .padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 8))
+
+ if secondaryButtonImageName != nil {
+ Spacer(minLength: Constants.textFieldImageSize * 2 + 8)
+ } else {
+ Spacer(minLength: buttonImageName != nil ? Constants.textFieldImageSize : 8)
+ }
+ }
+
+ if let buttonImageName = buttonImageName, let buttonAccessibilityLabel = buttonAccessibilityLabel {
+ let differenceBetweenImageSizeAndTapAreaPerEdge = (Constants.textFieldTapSize - Constants.textFieldImageSize) / 2.0
+ HStack(alignment: .center, spacing: 0) {
+ Spacer()
+
+ Button {
+ buttonAction?()
+ self.selectedCell = nil
+ } label: {
+ VStack(alignment: .trailing) {
+ Spacer()
+ HStack {
+ Spacer()
+ Image(buttonImageName)
+ .resizable()
+ .frame(width: Constants.textFieldImageSize, height: Constants.textFieldImageSize)
+ .foregroundColor(Color(UIColor.label).opacity(Constants.textFieldImageOpacity))
+ .opacity(subtitle.isEmpty ? 0 : 1)
+ Spacer()
+ }
+ Spacer()
+ }
+ }
+ .buttonStyle(.plain) // Prevent taps from being forwarded to the container view
+ // can't use .clear here or else both button padded area and container both respond to tap events
+ .background(BackgroundColor(isSelected: selectedCell == id).color.opacity(0))
+ .accessibilityLabel(buttonAccessibilityLabel)
+ .contentShape(Rectangle())
+ .frame(width: Constants.textFieldTapSize, height: Constants.textFieldTapSize)
+
+ if let secondaryButtonImageName = secondaryButtonImageName,
+ let secondaryButtonAccessibilityLabel = secondaryButtonAccessibilityLabel {
+ Button {
+ secondaryButtonAction?()
+ self.selectedCell = nil
+ } label: {
+ VStack(alignment: .trailing) {
+ Spacer()
+ HStack {
+ Spacer()
+ Image(secondaryButtonImageName)
+ .resizable()
+ .frame(width: Constants.textFieldImageSize, height: Constants.textFieldImageSize)
+ .foregroundColor(Color(UIColor.label).opacity(Constants.textFieldImageOpacity))
+ .opacity(subtitle.isEmpty ? 0 : 1)
+ Spacer()
+ }
+ Spacer()
+ }
+ }
+ .buttonStyle(.plain) // Prevent taps from being forwarded to the container view
+ .background(BackgroundColor(isSelected: selectedCell == id).color.opacity(0))
+ .accessibilityLabel(secondaryButtonAccessibilityLabel)
+ .contentShape(Rectangle())
+ .frame(width: Constants.textFieldTapSize, height: Constants.textFieldTapSize)
+ }
+
+ }
+ .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: -differenceBetweenImageSizeAndTapAreaPerEdge))
+ }
+ }
+ .selectableBackground(isSelected: selectedCell == id)
+ }
+}
+
+private extension View {
+ func copyable(isSelected: Bool) -> some View {
+ modifier(Copyable(isSelected: isSelected))
+ }
+
+ func selectableBackground(isSelected: Bool) -> some View {
+ modifier(SelectableBackground(isSelected: isSelected))
+ }
+}
+
+private struct Copyable: ViewModifier {
+ var isSelected: Bool
+
+ public func body(content: Content) -> some View {
+ ZStack {
+ Rectangle()
+ .foregroundColor(.clear)
+
+ content
+ .allowsHitTesting(false)
+ .contentShape(Rectangle())
+ .frame(maxWidth: .infinity)
+ .frame(minHeight: Constants.minRowHeight)
+ }
+ }
+}
+
+private struct SelectableBackground: ViewModifier {
+ var isSelected: Bool
+
+ public func body(content: Content) -> some View {
+ content
+ .listRowBackground(BackgroundColor(isSelected: isSelected).color)
+ .listRowInsets(.init(top: 0, leading: 16, bottom: 0, trailing: 16))
+ }
+}
+
+private struct ForegroundColor {
+ let isSelected: Bool
+
+ var color: Color {
+ if isSelected {
+ return .gray90
+ } else {
+ return .gray50
+ }
+ }
+}
+
+private struct BackgroundColor {
+ let isSelected: Bool
+
+ var color: Color {
+ if isSelected {
+ return Color("AutofillCellSelectedBackground")
+ } else {
+ return Color(designSystemColor: .surface)
+ }
+ }
+}
+
+private struct Constants {
+ static let verticalPadding: CGFloat = 4
+ static let minRowHeight: CGFloat = 60
+ static let textFieldImageOpacity: CGFloat = 0.84
+ static let textFieldImageSize: CGFloat = 24
+ static let textFieldTapSize: CGFloat = 36
+ static let insets = EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsViewController.swift
new file mode 100644
index 0000000000..cb4a37c95c
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsViewController.swift
@@ -0,0 +1,125 @@
+//
+// CredentialProviderListDetailsViewController.swift
+// DuckDuckGo
+//
+// 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 UIKit
+import SwiftUI
+import BrowserServicesKit
+import Common
+import Combine
+import Core
+
+protocol CredentialProviderListDetailsViewControllerDelegate: AnyObject {
+ func credentialProviderListDetailsViewControllerDidProvideText(_ controller: CredentialProviderListDetailsViewController, text: String)
+}
+
+class CredentialProviderListDetailsViewController: UIViewController {
+
+ private enum Constants {
+ static let padding: CGFloat = 16
+ }
+
+ weak var delegate: CredentialProviderListDetailsViewControllerDelegate?
+ private let viewModel: CredentialProviderListDetailsViewModel
+ private var cancellables: Set = []
+ private var contentView: UIView?
+
+ init(account: SecureVaultModels.WebsiteAccount? = nil, tld: TLD, shouldProvideTextToInsert: Bool = false) {
+ self.viewModel = CredentialProviderListDetailsViewModel(account: account,
+ tld: tld,
+ shouldProvideTextToInsert: shouldProvideTextToInsert)
+ super.init(nibName: nil, bundle: nil)
+ self.viewModel.delegate = self
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ var account: SecureVaultModels.WebsiteAccount? {
+ get {
+ viewModel.account
+ }
+ set {
+ if let newValue {
+ viewModel.updateData(with: newValue)
+ }
+ }
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ installSubviews()
+ setupCancellables()
+ setupNavigationBar()
+ }
+
+ private func installSubviews() {
+ installContentView()
+ }
+
+ private func setupCancellables() {
+ Publishers.MergeMany(
+ viewModel.$title,
+ viewModel.$username,
+ viewModel.$password,
+ viewModel.$address,
+ viewModel.$notes)
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] _ in
+ self?.setupNavigationBar()
+ }
+ .store(in: &cancellables)
+
+ }
+
+ private func installContentView() {
+ let contentView = CredentialProviderListDetailsView(viewModel: viewModel)
+ let hostingController = UIHostingController(rootView: contentView)
+ addChild(hostingController)
+ hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ hostingController.view.frame = view.bounds
+ view.addSubview(hostingController.view)
+ hostingController.didMove(toParent: self)
+ self.contentView = hostingController.view
+ }
+
+ private func setupNavigationBar() {
+ title = viewModel.navigationTitle
+ }
+
+ func showActionMessage(_ message: String) {
+ ActionMessageView.present(
+ message: message,
+ actionTitle: "",
+ onAction: {},
+ inView: self.view
+ )
+ }
+}
+
+extension CredentialProviderListDetailsViewController: CredentialProviderListDetailsViewModelDelegate {
+ func credentialProviderListDetailsViewModelDidProvideText(text: String) {
+ delegate?.credentialProviderListDetailsViewControllerDidProvideText(self, text: text)
+ }
+
+ func credentialProviderListDetailsViewModelShowActionMessage(message: String) {
+ showActionMessage(message)
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsViewModel.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsViewModel.swift
new file mode 100644
index 0000000000..33fac86061
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderListDetails/CredentialProviderListDetailsViewModel.swift
@@ -0,0 +1,192 @@
+//
+// CredentialProviderListDetailsViewModel.swift
+// DuckDuckGo
+//
+// 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 UIKit
+import SwiftUI
+import BrowserServicesKit
+import Common
+import Combine
+import Core
+
+protocol CredentialProviderListDetailsViewModelDelegate: AnyObject {
+ func credentialProviderListDetailsViewModelShowActionMessage(message: String)
+ func credentialProviderListDetailsViewModelDidProvideText(text: String)
+}
+
+final class CredentialProviderListDetailsViewModel: ObservableObject {
+ enum ViewMode {
+ case view
+ }
+
+ enum PasteboardCopyAction {
+ case username
+ case password
+ case address
+ case notes
+ }
+
+ weak var delegate: CredentialProviderListDetailsViewModelDelegate?
+ var account: SecureVaultModels.WebsiteAccount?
+
+ private let tld: TLD
+ private let autofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher()
+ private let autofillDomainNameUrlSort = AutofillDomainNameUrlSort()
+
+ @ObservedObject var headerViewModel: CredentialProviderListDetailsHeaderViewModel
+ @Published var isPasswordHidden = true
+ @Published var username = ""
+ @Published var password = ""
+ @Published var address = ""
+ @Published var notes = ""
+ @Published var title = ""
+ @Published var selectedCell: UUID?
+
+ private var passwordData: Data {
+ password.data(using: .utf8)!
+ }
+
+ var navigationTitle: String {
+ return title.isEmpty ? address : title
+ }
+
+ var websiteIsValidUrl: Bool {
+ account?.domain?.toTrimmedURL != nil
+ }
+
+ var userVisiblePassword: String {
+ let passwordHider = PasswordHider(password: password)
+ return isPasswordHidden ? passwordHider.hiddenPassword : passwordHider.password
+ }
+
+ var usernameDisplayString: String {
+ AutofillInterfaceEmailTruncator.truncateEmail(username, maxLength: 36)
+ }
+
+ let shouldProvideTextToInsert: Bool
+
+ internal init(account: SecureVaultModels.WebsiteAccount? = nil,
+ tld: TLD,
+ emailManager: EmailManager = EmailManager(),
+ shouldProvideTextToInsert: Bool) {
+ self.account = account
+ self.tld = tld
+ self.headerViewModel = CredentialProviderListDetailsHeaderViewModel()
+ self.shouldProvideTextToInsert = shouldProvideTextToInsert
+ if let account = account {
+ self.updateData(with: account)
+ }
+ }
+
+ func updateData(with account: SecureVaultModels.WebsiteAccount) {
+ self.account = account
+ username = account.username ?? ""
+ address = account.domain ?? ""
+ title = account.title ?? ""
+ notes = account.notes ?? ""
+ headerViewModel.updateData(with: account,
+ tld: tld,
+ autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher,
+ autofillDomainNameUrlSort: autofillDomainNameUrlSort)
+ setupPassword(with: account)
+ }
+
+ func copyToPasteboard(_ action: PasteboardCopyAction) {
+ var message = ""
+ switch action {
+ case .username:
+ message = UserText.credentialProviderDetailsCopyToastUsernameCopied
+ UIPasteboard.general.string = username
+ Pixel.fire(pixel: .autofillManagementCopyUsername)
+ case .password:
+ message = UserText.credentialProviderDetailsCopyToastPasswordCopied
+ UIPasteboard.general.string = password
+ Pixel.fire(pixel: .autofillManagementCopyPassword)
+ case .address:
+ message = UserText.credentialProviderDetailsCopyToastAddressCopied
+ UIPasteboard.general.string = address
+ case .notes:
+ message = UserText.credentialProviderDetailsCopyToastNotesCopied
+ UIPasteboard.general.string = notes
+ }
+
+ delegate?.credentialProviderListDetailsViewModelShowActionMessage(message: message)
+ }
+
+ func textToReturn(_ action: PasteboardCopyAction) {
+ var text = ""
+ switch action {
+ case .username:
+ text = username
+ case .password:
+ text = password
+ default:
+ return
+ }
+
+ delegate?.credentialProviderListDetailsViewModelDidProvideText(text: text)
+ }
+
+ private func setupPassword(with account: SecureVaultModels.WebsiteAccount) {
+ do {
+ if let accountID = account.id, let accountIdInt = Int64(accountID) {
+ let vault = try AutofillSecureVaultFactory.makeVault(reporter: nil)
+
+ if let credential = try
+ vault.websiteCredentialsFor(accountId: accountIdInt) {
+ self.password = credential.password.flatMap { String(data: $0, encoding: .utf8) } ?? ""
+ }
+ }
+ } catch {
+ Pixel.fire(pixel: .secureVaultError, error: error)
+ }
+ }
+
+ private func handleSecureVaultError(_ error: Error) {
+ Pixel.fire(pixel: .secureVaultError, error: error)
+ }
+}
+
+final class CredentialProviderListDetailsHeaderViewModel: ObservableObject {
+ private var dateFormatter: DateFormatter = {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateStyle = .medium
+ dateFormatter.timeStyle = .short
+ return dateFormatter
+ }()
+
+ @Published var title: String = ""
+ @Published var subtitle: String = ""
+ @Published var domain: String = ""
+ @Published var favicon: UIImage = UIImage(named: "Logo")!
+
+ func updateData(with account: SecureVaultModels.WebsiteAccount, tld: TLD, autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher, autofillDomainNameUrlSort: AutofillDomainNameUrlSort) {
+ self.title = account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher)
+ self.subtitle = UserText.credentialProviderDetailsLastUpdated(for: (dateFormatter.string(from: account.lastUpdated)))
+ self.domain = account.domain ?? ""
+
+ // Update favicon
+ let accountName = account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher)
+ let accountTitle = (account.title?.isEmpty == false) ? account.title! : "#"
+ let preferredFakeFaviconLetters = tld.eTLDplus1(accountName) ?? accountTitle
+ if let image = FaviconHelper.loadImageFromCache(forDomain: domain, preferredFakeFaviconLetters: preferredFakeFaviconLetters) {
+ self.favicon = image
+ }
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift
new file mode 100644
index 0000000000..557b7292e7
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift
@@ -0,0 +1,240 @@
+//
+// CredentialProviderViewController.swift
+// DuckDuckGo
+//
+// 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 AuthenticationServices
+import SwiftUI
+import BrowserServicesKit
+import Core
+import Common
+import os.log
+
+class CredentialProviderViewController: ASCredentialProviderViewController {
+
+ private struct Constants {
+ static let openPasswords = AppDeepLinkSchemes.openPasswords.url
+ }
+
+ private lazy var authenticator = UserAuthenticator(reason: UserText.credentialProviderListAuthenticationReason,
+ cancelTitle: UserText.credentialProviderListAuthenticationCancelButton)
+
+ private lazy var credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging = AutofillCredentialIdentityStoreManager(credentialStore: ASCredentialIdentityStore.shared,
+ vault: secureVault,
+ tld: tld)
+
+ private lazy var secureVault: (any AutofillSecureVault)? = {
+ if findKeychainItemsWithV4() {
+ return try? AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter())
+ } else {
+ return nil
+ }
+ }()
+
+ private lazy var tld: TLD = TLD()
+
+ private lazy var vaultCredentialManager: VaultCredentialManaging = VaultCredentialManager(secureVault: secureVault,
+ credentialIdentityStoreManager: credentialIdentityStoreManager)
+
+ // MARK: - ASCredentialProviderViewController Overrides
+
+ override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
+ loadCredentialsList(for: serviceIdentifiers)
+ }
+
+ override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
+ // A quirk here is calling .canAuthenticate in this one scenario actually triggers the prompt to authentication
+ // Calling .authenticate here results in the extension attempting to present a non-existent view controller causing weird UI
+ if authenticator.canAuthenticateViaBiometrics() {
+ provideCredential(for: credentialIdentity)
+ } else {
+ self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.userInteractionRequired.rawValue))
+ }
+ }
+
+ @available(iOS 17.0, *)
+ override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) {
+ guard credentialRequest.type == .password else {
+ self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.credentialIdentityNotFound.rawValue))
+ return
+ }
+
+ if authenticator.canAuthenticateViaBiometrics() {
+ provideCredential(for: credentialRequest.credentialIdentity)
+ } else {
+ self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.userInteractionRequired.rawValue))
+ }
+ }
+
+ override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
+ let hostingController = UIHostingController(rootView: LockScreenView())
+ installChildViewController(hostingController)
+
+ authenticateAndHandleCredential {
+ self.provideCredential(for: credentialIdentity)
+ }
+ }
+
+ @available(iOS 17.0, *)
+ override func prepareInterfaceToProvideCredential(for credentialRequest: any ASCredentialRequest) {
+ let hostingController = UIHostingController(rootView: LockScreenView())
+ installChildViewController(hostingController)
+
+ authenticateAndHandleCredential {
+ self.provideCredential(for: credentialRequest.credentialIdentity)
+ }
+ }
+
+ override func prepareInterfaceForExtensionConfiguration() {
+ let viewModel = CredentialProviderActivatedViewModel { [weak self] shouldLaunchApp in
+ if shouldLaunchApp {
+ self?.openUrl(Constants.openPasswords)
+ }
+ self?.extensionContext.completeExtensionConfigurationRequest()
+ }
+
+ let view = CredentialProviderActivatedView(viewModel: viewModel)
+ let hostingController = UIHostingController(rootView: view)
+ installChildViewController(hostingController)
+
+ Task {
+ if findKeychainItemsWithV4() {
+ await credentialIdentityStoreManager.populateCredentialStore()
+ }
+ }
+
+ Pixel.fire(pixel: .autofillExtensionEnabled)
+ }
+
+ @available(iOSApplicationExtension 18.0, *)
+ override func prepareInterfaceForUserChoosingTextToInsert() {
+ loadCredentialsList(for: [], shouldProvideTextToInsert: true)
+ }
+
+ // MARK: - Private
+
+ private func loadCredentialsList(for serviceIdentifiers: [ASCredentialServiceIdentifier], shouldProvideTextToInsert: Bool = false) {
+ let credentialProviderListViewController = CredentialProviderListViewController(serviceIdentifiers: serviceIdentifiers,
+ secureVault: secureVault,
+ credentialIdentityStoreManager: credentialIdentityStoreManager,
+ shouldProvideTextToInsert: shouldProvideTextToInsert,
+ tld: tld,
+ onRowSelected: { [weak self] item in
+ guard let self = self else {
+ self?.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.failed.rawValue))
+ return
+ }
+
+ let credential = self.vaultCredentialManager.fetchCredential(for: item.account)
+
+ self.extensionContext.completeRequest(withSelectedCredential: credential, completionHandler: nil)
+
+ }, onTextProvided: { [weak self] text in
+ if #available(iOSApplicationExtension 18.0, *) {
+ self?.extensionContext.completeRequest(withTextToInsert: text)
+ }
+ }, onDismiss: {
+ self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.userCanceled.rawValue))
+ })
+
+ let navigationController = UINavigationController(rootViewController: credentialProviderListViewController)
+ self.view.subviews.forEach { $0.removeFromSuperview() }
+ addChild(navigationController)
+ navigationController.view.frame = self.view.bounds
+ navigationController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ self.view.addSubview(navigationController.view)
+ navigationController.didMove(toParent: self)
+ }
+
+ @available(iOS 17.0, *)
+ private func provideCredential(for credentialIdentity: ASCredentialIdentity) {
+ guard let passwordCredential = vaultCredentialManager.fetchCredential(for: credentialIdentity) else {
+ self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.credentialIdentityNotFound.rawValue))
+ Pixel.fire(pixel: .autofillExtensionQuickTypeCancelled)
+ return
+ }
+
+ self.extensionContext.completeRequest(withSelectedCredential: passwordCredential)
+ Pixel.fire(pixel: .autofillExtensionQuickTypeConfirmed)
+ }
+
+ private func provideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
+ guard let passwordCredential = vaultCredentialManager.fetchCredential(for: credentialIdentity) else {
+ self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.credentialIdentityNotFound.rawValue))
+ Pixel.fire(pixel: .autofillExtensionQuickTypeCancelled)
+ return
+ }
+
+ self.extensionContext.completeRequest(withSelectedCredential: passwordCredential)
+ Pixel.fire(pixel: .autofillExtensionQuickTypeConfirmed)
+ }
+
+ private func authenticateAndHandleCredential(provideCredential: @escaping () -> Void) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
+ self?.authenticator.authenticate { error in
+ if error != nil {
+ if error != .noAuthAvailable {
+ self?.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.userInteractionRequired.rawValue))
+ } else {
+ let alert = UIAlertController.makeDeviceAuthenticationAlert { [weak self] in
+ self?.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain,
+ code: ASExtensionError.userInteractionRequired.rawValue))
+ }
+ self?.present(alert, animated: true)
+ }
+ } else {
+ provideCredential()
+ }
+ }
+ }
+ }
+
+ private func findKeychainItemsWithV4() -> Bool {
+ var itemsWithV4: [String] = []
+
+ let query: [String: Any] = [
+ kSecClass as String: kSecClassGenericPassword,
+ kSecReturnAttributes as String: kCFBooleanTrue!,
+ kSecMatchLimit as String: kSecMatchLimitAll
+ ]
+
+ var result: AnyObject?
+
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
+
+ if status == errSecSuccess, let items = result as? [[String: Any]] {
+ for item in items {
+ if let service = item[kSecAttrService as String] as? String,
+ service.contains("v4") {
+ itemsWithV4.append(service)
+ }
+ }
+ } else {
+ Logger.autofill.debug("No items found or error: \(status)")
+ }
+
+ return !itemsWithV4.isEmpty
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift
new file mode 100644
index 0000000000..2908e6e5d9
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift
@@ -0,0 +1,51 @@
+//
+// UIAlertControllerExtension.swift
+// DuckDuckGo
+//
+// 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 UIKit
+
+extension UIAlertController {
+
+ static func makeDeviceAuthenticationAlert(completion: @escaping () -> Void) -> UIAlertController {
+
+ let deviceType: String
+
+ switch UIDevice.current.userInterfaceIdiom {
+ case .pad:
+ deviceType = UserText.deviceTypeiPad
+ case .phone:
+ deviceType = UserText.deviceTypeiPhone
+ default:
+ deviceType = UserText.deviceTypeDefault
+ }
+
+ let alertController = UIAlertController(
+ title: UserText.credentialProviderNoDeviceAuthSetTitle,
+ message: String(format: UserText.credentialProviderNoDeviceAuthSetMessage, deviceType),
+ preferredStyle: .alert
+ )
+
+ let closeButton = UIAlertAction(title: UserText.actionClose, style: .default) { _ in
+ completion()
+ }
+
+ alertController.addAction(closeButton)
+ return alertController
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIColorExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIColorExtension.swift
new file mode 100644
index 0000000000..e1fe4656f2
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIColorExtension.swift
@@ -0,0 +1,67 @@
+//
+// UIColorExtension.swift
+// DuckDuckGo
+//
+// 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 UIKit
+
+extension UIColor {
+
+ convenience init(hex: String) {
+ var rgbValue: UInt64 = 0
+ Scanner(string: hex).scanHexInt64(&rgbValue)
+
+ self.init(
+ red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
+ green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
+ blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
+ alpha: CGFloat(1.0)
+ )
+ }
+
+ static func forDomain(_ domain: String) -> UIColor {
+ var consistentHash: Int {
+ return domain.utf8
+ .map { return $0 }
+ .reduce(5381) { ($0 << 5) &+ $0 &+ Int($1) }
+ }
+
+ let palette = [
+ UIColor(hex: "94B3AF"),
+ UIColor(hex: "727998"),
+ UIColor(hex: "645468"),
+ UIColor(hex: "4D5F7F"),
+ UIColor(hex: "855DB6"),
+ UIColor(hex: "5E5ADB"),
+ UIColor(hex: "678FFF"),
+ UIColor(hex: "6BB4EF"),
+ UIColor(hex: "4A9BAE"),
+ UIColor(hex: "66C4C6"),
+ UIColor(hex: "55D388"),
+ UIColor(hex: "99DB7A"),
+ UIColor(hex: "ECCC7B"),
+ UIColor(hex: "E7A538"),
+ UIColor(hex: "DD6B4C"),
+ UIColor(hex: "D65D62")
+ ]
+
+ let hash = consistentHash
+ let index = hash % palette.count
+ return palette[abs(index)]
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift
new file mode 100644
index 0000000000..65aa041a9c
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift
@@ -0,0 +1,42 @@
+//
+// UIResponderExtension.swift
+// DuckDuckGo
+//
+// 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 UIKit
+
+extension UIResponder {
+
+ /// Attempts to open a URL using the UIApplication instance in the responder chain.
+ /// This is required as the CredentialProvider extension context cannot directly launch the host app.
+ /// - Returns: True if the URL was opened successfully, false otherwise.
+ @discardableResult
+ func openUrl(_ url: URL?) -> Bool {
+ guard let url = url else { return false }
+
+ var responder: UIResponder? = self
+ while let r = responder {
+ if let application = r as? UIApplication {
+ application.open(url, options: [:], completionHandler: nil)
+ return true
+ }
+ responder = r.next
+ }
+
+ return false
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift
new file mode 100644
index 0000000000..0bba578770
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift
@@ -0,0 +1,39 @@
+//
+// UIViewControllerExtension.swift
+// DuckDuckGo
+//
+// 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 UIKit
+
+extension UIViewController {
+
+ public func installChildViewController(_ childController: UIViewController) {
+ addChild(childController)
+ view.addSubview(childController.view)
+
+ childController.view.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ childController.view.topAnchor.constraint(equalTo: view.topAnchor),
+ childController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ childController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ childController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
+ ])
+
+ childController.didMove(toParent: self)
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UImageExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UImageExtension.swift
new file mode 100644
index 0000000000..56cda53ea2
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UImageExtension.swift
@@ -0,0 +1,30 @@
+//
+// UImageExtension.swift
+// DuckDuckGo
+//
+// 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 UIKit
+
+extension UIImage {
+
+ func toSRGB() -> UIImage {
+ UIGraphicsImageRenderer(size: size).image { _ in
+ draw(in: CGRect(origin: .zero, size: size))
+ }
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillCellSelectedBackground.colorset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillCellSelectedBackground.colorset/Contents.json
new file mode 100644
index 0000000000..cc91bfda6f
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillCellSelectedBackground.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "display-p3",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.800",
+ "green" : "0.800",
+ "red" : "0.800"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "display-p3",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.267",
+ "green" : "0.267",
+ "red" : "0.267"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/AutofillLock.pdf b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/AutofillLock.pdf
new file mode 100644
index 0000000000..606eb7cbd1
Binary files /dev/null and b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/AutofillLock.pdf differ
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/Contents.json
new file mode 100644
index 0000000000..3ed495a79d
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "AutofillLock.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000000..73c00596a7
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Copy-24.imageset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Copy-24.imageset/Contents.json
new file mode 100644
index 0000000000..1a533a908e
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Copy-24.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "Copy-24.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Copy-24.imageset/Copy-24.pdf b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Copy-24.imageset/Copy-24.pdf
new file mode 100644
index 0000000000..56407ef84b
Binary files /dev/null and b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Copy-24.imageset/Copy-24.pdf differ
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-24.imageset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-24.imageset/Contents.json
new file mode 100644
index 0000000000..d46a04ef5b
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-24.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "Eye-24.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-24.imageset/Eye-24.pdf b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-24.imageset/Eye-24.pdf
new file mode 100644
index 0000000000..ac69e29aa6
Binary files /dev/null and b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-24.imageset/Eye-24.pdf differ
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-Closed-24.imageset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-Closed-24.imageset/Contents.json
new file mode 100644
index 0000000000..ac2612ee62
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-Closed-24.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "Eye-Closed-24.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-Closed-24.imageset/Eye-Closed-24.pdf b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-Closed-24.imageset/Eye-Closed-24.pdf
new file mode 100644
index 0000000000..78b200d65b
Binary files /dev/null and b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Eye-Closed-24.imageset/Eye-Closed-24.pdf differ
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Logo.imageset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Logo.imageset/Contents.json
new file mode 100644
index 0000000000..5cf237b7f3
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Logo.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Dax_default.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Logo.imageset/Dax_default.pdf b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Logo.imageset/Dax_default.pdf
new file mode 100644
index 0000000000..46ebb1057c
Binary files /dev/null and b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Logo.imageset/Dax_default.pdf differ
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-Add-96x96.imageset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-Add-96x96.imageset/Contents.json
new file mode 100644
index 0000000000..9c63920d3e
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-Add-96x96.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Passwords-Add-96x96.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-Add-96x96.imageset/Passwords-Add-96x96.svg b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-Add-96x96.imageset/Passwords-Add-96x96.svg
new file mode 100644
index 0000000000..657f2681fc
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-Add-96x96.imageset/Passwords-Add-96x96.svg
@@ -0,0 +1,19 @@
+
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Contents.json
new file mode 100644
index 0000000000..608aeb0457
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Passwords-DDG-96x96.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Passwords-DDG-96x96.svg b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Passwords-DDG-96x96.svg
new file mode 100644
index 0000000000..18521b98ac
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Passwords-DDG-96x96.svg
@@ -0,0 +1,26 @@
+
diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift
new file mode 100644
index 0000000000..9068764c55
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift
@@ -0,0 +1,86 @@
+//
+// UserText.swift
+// DuckDuckGo
+//
+// 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 UserText {
+
+ static let credentialProviderActivatedTitle = NSLocalizedString("credential.provider.activated.title", value: "Autofill Passwords activated!", comment: "The title of the screen confirming DuckDuckGo can now be used for autofilling passwords")
+
+ static let credentialProviderActivatedButton = NSLocalizedString("credential.provider.activated.button", value: "Open DuckDuckGo", comment: "Title of button to launch the DuckDuckGo app")
+
+ static let actionClose = NSLocalizedString("action.button.close", value: "Close", comment: "Close button title")
+
+ static let actionDone = NSLocalizedString("action.button.done", value: "Done", comment: "Done button title")
+
+ static let credentialProviderListTitle = NSLocalizedString("credential.provider.list.title", value: "Passwords", comment: "Title for screen listing autofill logins")
+
+ static let credentialProviderListPrompt = NSLocalizedString("credential.provider.list.prompt", value: "Choose a password to use for \"%@\"", comment: "Prompt above the title for screen listing autofill logins, example: Choose a password to use for \"website.com\"")
+
+ static let credentialProviderListSearchPlaceholder = NSLocalizedString("credential.provider.list.search-placeholder", value: "Search passwords", comment: "Placeholder for search field on autofill login listing")
+
+ static let credentialProviderListEmptyViewTitle = NSLocalizedString("credential.provider.list.empty-view.title", value: "No passwords saved yet", comment: "Title for view displayed when autofill has no items")
+
+ static let credentialProviderListEmptyViewSubtitle = NSLocalizedString("credential.provider.list.empty-view.footer", value: "Passwords are stored securely on your device.", comment: "Footer label displayed below table section with option to enable autofill")
+
+ static let credentialProviderListSuggested = NSLocalizedString("credential.provider.list.suggested", value: "Suggested", comment: "Section title for group of suggested saved logins")
+
+ static let credentialProviderListSearchNoResultTitle = NSLocalizedString("credential.provider.list.search.no-results.title", value: "No Results", comment: "Title displayed when there are no results on Autofill search")
+
+ static func credentialProviderListSearchNoResultSubtitle(for query: String) -> String {
+ let message = NSLocalizedString("credential.provider.list.search.no-results.subtitle", value: "for '%@'", comment: "Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle)")
+ return message.format(arguments: query)
+ }
+
+ static let credentialProviderListAuthenticationReason = NSLocalizedString("credential.provider.list.auth.reason", value: "Unlock device to access passwords", comment: "Reason for auth when opening screen with list of saved passwords")
+
+ static let credentialProviderListAuthenticationCancelButton = NSLocalizedString("credential.provider.list.auth.cancel", value: "Cancel", comment: "Cancel button for auth when opening login list")
+
+ static let credentialProviderNoDeviceAuthSetTitle = NSLocalizedString("credential.provider.no-device-auth-set.title", value: "Device Passcode Required", comment: "Title for alert when device authentication is not set")
+
+ static let credentialProviderNoDeviceAuthSetMessage = NSLocalizedString("credential.provider.no-device-auth-set.message", value: "Set a passcode on %@ to autofill your DuckDuckGo passwords.", comment: "Message for alert when device authentication is not set, where %@ is iPhone|iPad|device")
+
+ static let deviceTypeiPhone = NSLocalizedString("credential.provider.device.type.iphone", value: "iPhone", comment: "Device type is iPhone")
+ static let deviceTypeiPad = NSLocalizedString("credential.provider.device.type.pad", value: "iPad", comment: "Device type is iPad")
+ static let deviceTypeDefault = NSLocalizedString("credential.provider.device.type.default", value: "device", comment: "Default string used if users device is not iPhone or iPad")
+
+ public static let credentialProviderDetailsCopyToastUsernameCopied = NSLocalizedString("credential.provider.list.details.copy-toast.username-copied", value: "Username copied", comment: "Title for toast when copying username")
+ public static let credentialProviderDetailsCopyToastPasswordCopied = NSLocalizedString("credential.provider.list.details.copy-toast.password-copied", value: "Password copied", comment: "Title for toast when copying password")
+ public static let credentialProviderDetailsCopyToastAddressCopied = NSLocalizedString("credential.provider.list.details.copy-toast.address-copied", value: "Address copied", comment: "Title for toast when copying address")
+ public static let credentialProviderDetailsCopyToastNotesCopied = NSLocalizedString("credential.provider.list.details.copy-toast.notes-copied", value: "Notes copied", comment: "Title for toast when copying notes")
+
+ public static func credentialProviderDetailsLastUpdated(for date: String) -> String {
+ let message = NSLocalizedString("credential.provider.list.details.last-updated", value: "Last updated %@", comment: "Message displaying when the login was last updated")
+ return message.format(arguments: date)
+ }
+
+ public static let credentialProviderDetailsLoginName = NSLocalizedString("credential.provider.list.details.login-name", value: "Title", comment: "Login name label for login details on autofill")
+ public static let credentialProviderDetailsUsername = NSLocalizedString("credential.provider.list.details.username", value: "Username", comment: "Username label for login details on autofill")
+ public static let credentialProviderDetailsPassword = NSLocalizedString("credential.provider.list.details.password", value: "Password", comment: "Password label for login details on autofill")
+ public static let credentialProviderDetailsAddress = NSLocalizedString("credential.provider.list.details.address", value: "Website URL", comment: "Address label for login details on autofill")
+ public static let credentialProviderDetailsNotes = NSLocalizedString("credential.provider.list.details.notes", value: "Notes", comment: "Notes label for login details on autofill")
+
+
+ public static func credentialProviderDetailsCopyPrompt(for type: String) -> String {
+ let message = NSLocalizedString("credential.provider.list.details.copy-prompt", value: "Copy %@", comment: "Menu item text for copying autofill login details")
+ return message.format(arguments: type)
+ }
+ public static let credentialProviderDetailsShowPassword = NSLocalizedString("credential.provider.list.details.show-password", value: "Show Password", comment: "Accessibility title for a Show Password button displaying actial password instead of *****")
+ public static let credentialProviderDetailsHidePassword = NSLocalizedString("credential.provider.list.details.hide-password", value: "Hide Password", comment: "Accessibility title for a Hide Password button replacing displayed password with *****")
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/ActionMessageView.swift b/AutofillCredentialProvider/CredentialProvider/Shared/ActionMessageView.swift
new file mode 100644
index 0000000000..50a1b38a4e
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Shared/ActionMessageView.swift
@@ -0,0 +1,197 @@
+//
+// ActionMessageView.swift
+// DuckDuckGo
+//
+// Copyright © 2021 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 UIKit
+
+extension ActionMessageView: NibLoading {}
+
+class ActionMessageView: UIView {
+
+ enum PresentationLocation {
+ case withBottomBar(andAddressBarBottom: Bool)
+ case withoutBottomBar
+ }
+
+ private static var presentedMessages = [ActionMessageView]()
+
+ private enum Constants {
+ static var maxWidth: CGFloat = 346
+ static var minimumHorizontalPadding: CGFloat = 20
+ static var cornerRadius: CGFloat = 10
+
+ static var animationDuration: TimeInterval = 0.2
+ static var duration: TimeInterval = 3.0
+
+ static var windowBottomPaddingWithBottomBar: CGFloat {
+ if UIDevice.current.userInterfaceIdiom == .phone && !UIDevice.current.orientation.isPortrait {
+ return 40
+ }
+
+ return 70
+ }
+
+ static var windowBottomPaddingWithAddressBar: CGFloat {
+ return windowBottomPaddingWithBottomBar + 52
+ }
+
+ static var windowBottomPaddingWithoutBottomBar: CGFloat {
+ return 0
+ }
+
+ }
+
+ private static func bottomPadding(for location: PresentationLocation) -> CGFloat {
+ switch location {
+ case .withBottomBar(let isAddressBarBottom):
+ return isAddressBarBottom ? Constants.windowBottomPaddingWithAddressBar : Constants.windowBottomPaddingWithBottomBar
+ case .withoutBottomBar:
+ return Constants.windowBottomPaddingWithoutBottomBar
+ }
+ }
+
+ @IBOutlet weak var message: UILabel!
+ @IBOutlet weak var actionButton: UIButton!
+
+ @IBOutlet var labelToButton: NSLayoutConstraint!
+ @IBOutlet var labelToTrailing: NSLayoutConstraint!
+
+ private var action: () -> Void = {}
+ private var onDidDismiss: () -> Void = {}
+
+ private var dismissWorkItem: DispatchWorkItem?
+
+ static func loadFromXib() -> ActionMessageView {
+ return ActionMessageView.load(nibName: "ActionMessageView")
+ }
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ layer.cornerRadius = Constants.cornerRadius
+ }
+
+ static func present(message: NSAttributedString,
+ numberOfLines: Int = 0,
+ actionTitle: String? = nil,
+ presentationLocation: PresentationLocation = .withBottomBar(andAddressBarBottom: false),
+ duration: TimeInterval = Constants.duration,
+ onAction: @escaping () -> Void = {},
+ onDidDismiss: @escaping () -> Void = {},
+ inView: UIView) {
+ let messageView = loadFromXib()
+ messageView.message.attributedText = message
+ messageView.message.numberOfLines = numberOfLines
+ ActionMessageView.present(messageView: messageView,
+ message: message.string,
+ actionTitle: actionTitle,
+ presentationLocation: presentationLocation,
+ duration: duration,
+ onAction: onAction,
+ onDidDismiss: onDidDismiss,
+ inView: inView)
+ }
+
+ static func present(message: String,
+ actionTitle: String? = nil,
+ presentationLocation: PresentationLocation = .withBottomBar(andAddressBarBottom: false),
+ duration: TimeInterval = Constants.duration,
+ onAction: @escaping () -> Void = {},
+ onDidDismiss: @escaping () -> Void = {},
+ inView: UIView) {
+ let messageView = loadFromXib()
+ messageView.message.text = message
+ ActionMessageView.present(messageView: messageView,
+ message: message,
+ actionTitle: actionTitle,
+ presentationLocation: presentationLocation,
+ duration: duration,
+ onAction: onAction,
+ onDidDismiss: onDidDismiss,
+ inView: inView)
+ }
+
+ private static func present(messageView: ActionMessageView,
+ message: String,
+ actionTitle: String? = nil,
+ presentationLocation: PresentationLocation = .withBottomBar(andAddressBarBottom: false),
+ duration: TimeInterval = Constants.duration,
+ onAction: @escaping () -> Void = {},
+ onDidDismiss: @escaping () -> Void = {},
+ inView: UIView) {
+ dismissAllMessages()
+
+ if let actionTitle = actionTitle, let title = messageView.actionButton.attributedTitle(for: .normal) {
+ messageView.actionButton.setAttributedTitle(title.withText(actionTitle), for: .normal)
+ messageView.action = onAction
+ } else {
+ messageView.labelToButton.isActive = false
+ messageView.labelToTrailing.isActive = true
+ messageView.actionButton.isHidden = true
+ }
+ messageView.onDidDismiss = onDidDismiss
+
+ inView.addSubview(messageView)
+ inView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: messageView.bottomAnchor,
+ constant: bottomPadding(for: presentationLocation)).isActive = true
+
+ let messageViewWidth = inView.frame.width <= Constants.maxWidth ? inView.frame.width - Constants.minimumHorizontalPadding : Constants.maxWidth
+ messageView.widthAnchor.constraint(equalToConstant: messageViewWidth).isActive = true
+ messageView.centerXAnchor.constraint(equalTo: inView.centerXAnchor).isActive = true
+
+ inView.layoutIfNeeded()
+
+ messageView.alpha = 0
+ UIView.animate(withDuration: Constants.animationDuration) {
+ messageView.alpha = 1
+ } completion: { _ in
+ UIAccessibility.post(notification: .announcement, argument: message)
+ }
+
+ let workItem = DispatchWorkItem { [weak messageView] in
+ messageView?.dismissAndFadeOut()
+ }
+ messageView.dismissWorkItem = workItem
+ DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: workItem)
+ presentedMessages.append(messageView)
+ }
+
+ static func dismissAllMessages() {
+ presentedMessages.forEach { $0.dismissAndFadeOut() }
+ }
+
+ func dismissAndFadeOut() {
+ dismissWorkItem?.cancel()
+ dismissWorkItem = nil
+
+ UIView.animate(withDuration: Constants.animationDuration, animations: {
+ self.alpha = 0
+ }, completion: { _ in
+ self.removeFromSuperview()
+ if let position = Self.presentedMessages.firstIndex(of: self) {
+ Self.presentedMessages.remove(at: position)
+ }
+ self.onDidDismiss()
+ })
+ }
+
+ @IBAction func onButtonTap() {
+ action()
+ dismissAndFadeOut()
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/ActionMessageView.xib b/AutofillCredentialProvider/CredentialProvider/Shared/ActionMessageView.xib
new file mode 100644
index 0000000000..2c532dc85f
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Shared/ActionMessageView.xib
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/FaviconHelper.swift b/AutofillCredentialProvider/CredentialProvider/Shared/FaviconHelper.swift
new file mode 100644
index 0000000000..b1d95ef7a8
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Shared/FaviconHelper.swift
@@ -0,0 +1,87 @@
+//
+// FaviconHelper.swift
+// DuckDuckGo
+//
+// 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 Common
+import Core
+import UIKit
+
+struct FaviconHelper {
+
+ static func loadImageFromCache(forDomain domain: String?, preferredFakeFaviconLetters: String) -> UIImage? {
+ guard let domain = domain,
+ let cacheUrl = FaviconsCacheType.fireproof.cacheLocation() else { return nil }
+
+ let key = FaviconHasher.createHash(ofDomain: domain)
+
+ // Slight leap here to avoid loading Kingfisher as a library for the widgets.
+ // Once dependency management is fixed, link it and use Favicons directly.
+ let imageUrl = cacheUrl.appendingPathComponent("com.onevcat.Kingfisher.ImageCache.fireproof").appendingPathComponent(key)
+
+ guard let data = (try? Data(contentsOf: imageUrl)) else {
+ let image = Self.createFakeFavicon(forDomain: domain, size: 32, backgroundColor: UIColor.forDomain(domain), preferredFakeFaviconLetters: preferredFakeFaviconLetters)
+ return image
+ }
+
+ return UIImage(data: data)?.toSRGB()
+ }
+
+ private static func createFakeFavicon(forDomain domain: String,
+ size: CGFloat = 192,
+ backgroundColor: UIColor = UIColor.red,
+ bold: Bool = true,
+ preferredFakeFaviconLetters: String? = nil,
+ letterCount: Int = 2) -> UIImage? {
+
+ let cornerRadius = size * 0.125
+ let imageRect = CGRect(x: 0, y: 0, width: size, height: size)
+ let padding = size * 0.16
+ let labelFrame = CGRect(x: padding, y: padding, width: imageRect.width - (2 * padding), height: imageRect.height - (2 * padding))
+
+ let renderer = UIGraphicsImageRenderer(size: imageRect.size)
+ let icon = renderer.image { imageContext in
+ let context = imageContext.cgContext
+
+ context.setFillColor(backgroundColor.cgColor)
+ context.addPath(CGPath(roundedRect: imageRect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil))
+ context.fillPath()
+
+ let label = UILabel(frame: labelFrame)
+ label.numberOfLines = 1
+ label.adjustsFontSizeToFitWidth = true
+ label.minimumScaleFactor = 0.1
+ label.baselineAdjustment = .alignCenters
+ label.font = bold ? UIFont.boldSystemFont(ofSize: size) : UIFont.systemFont(ofSize: size)
+ label.textColor = .white
+ label.textAlignment = .center
+
+ if let preferedPrefix = preferredFakeFaviconLetters?.droppingWwwPrefix().prefix(letterCount).capitalized {
+ label.text = preferedPrefix
+ } else {
+ label.text = preferredFakeFaviconLetters?.capitalized ?? "#"
+ }
+
+ context.translateBy(x: padding, y: padding)
+
+ label.layer.draw(in: context)
+ }
+
+ return icon.withRenderingMode(.alwaysOriginal)
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/LockScreenView.swift b/AutofillCredentialProvider/CredentialProvider/Shared/LockScreenView.swift
new file mode 100644
index 0000000000..05db061e8d
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Shared/LockScreenView.swift
@@ -0,0 +1,43 @@
+//
+// LockScreenView.swift
+// DuckDuckGo
+//
+// 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 SwiftUI
+import DesignResourcesKit
+
+struct LockScreenView: View {
+ var body: some View {
+ GeometryReader { geometry in
+ VStack {
+ Spacer()
+ Image(.autofillLock)
+ .position(x: geometry.size.width / 2,
+ y: shouldCenterVerticallyInLandscape(on: geometry) ? geometry.size.height / 2 : geometry.size.height * 0.8)
+ }
+ }
+ .background(Color(designSystemColor: .background))
+ }
+
+ private func shouldCenterVerticallyInLandscape(on geometry: GeometryProxy) -> Bool {
+ return UIDevice.current.userInterfaceIdiom == .phone && geometry.size.width > geometry.size.height
+ }
+}
+
+#Preview {
+ LockScreenView()
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/NibLoading.swift b/AutofillCredentialProvider/CredentialProvider/Shared/NibLoading.swift
new file mode 100644
index 0000000000..af24b38f7c
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Shared/NibLoading.swift
@@ -0,0 +1,34 @@
+//
+// NibLoading.swift
+// DuckDuckGo
+//
+// Copyright © 2017 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 UIKit
+
+protocol NibLoading {
+}
+
+extension NibLoading where Self: UIView {
+ static func load(nibName: String) -> Self {
+ let nib = UINib(nibName: nibName, bundle: nil)
+ guard let view = nib.instantiate(withOwner: self, options: nil).first as? Self else {
+ fatalError("Error instantiating view")
+ }
+ view.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight]
+ return view
+ }
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/SecureVaultReporter.swift b/AutofillCredentialProvider/CredentialProvider/Shared/SecureVaultReporter.swift
new file mode 100644
index 0000000000..2a001a714f
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Shared/SecureVaultReporter.swift
@@ -0,0 +1,44 @@
+//
+// SecureVaultReporter.swift
+// DuckDuckGo
+//
+// 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 Common
+import Core
+import Foundation
+import SecureStorage
+
+final class SecureVaultReporter: SecureVaultReporting {
+
+ func secureVaultError(_ error: SecureStorage.SecureStorageError) {
+#if DEBUG
+ guard !ProcessInfo().arguments.contains("testing") else { return }
+#endif
+ let pixelParams = [PixelParameters.isBackgrounded: "false",
+ PixelParameters.appVersion: AppVersion.shared.versionAndBuildNumber]
+
+ switch error {
+ case .initFailed(let error):
+ DailyPixel.fire(pixel: .secureVaultInitFailedError, error: error, withAdditionalParameters: pixelParams)
+ case .failedToOpenDatabase(let error):
+ DailyPixel.fire(pixel: .secureVaultFailedToOpenDatabaseError, error: error, withAdditionalParameters: pixelParams)
+ default:
+ DailyPixel.fire(pixel: .secureVaultError, error: error)
+ }
+ }
+
+}
diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/VaultCredentialManager.swift b/AutofillCredentialProvider/CredentialProvider/Shared/VaultCredentialManager.swift
new file mode 100644
index 0000000000..945d58456a
--- /dev/null
+++ b/AutofillCredentialProvider/CredentialProvider/Shared/VaultCredentialManager.swift
@@ -0,0 +1,111 @@
+//
+// VaultCredentialManager.swift
+// DuckDuckGo
+//
+// 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 AuthenticationServices
+import BrowserServicesKit
+import Core
+import SecureStorage
+
+protocol VaultCredentialManaging: AnyObject {
+ func fetchCredential(for account: SecureVaultModels.WebsiteAccount) -> ASPasswordCredential
+ func fetchCredential(for identity: ASPasswordCredentialIdentity) -> ASPasswordCredential?
+ @available(iOS 17.0, *)
+ func fetchCredential(for identity: ASCredentialIdentity) -> ASPasswordCredential?
+}
+
+final class VaultCredentialManager: VaultCredentialManaging {
+
+ private let secureVault: (any AutofillSecureVault)?
+ private let credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging
+
+ init(secureVault: (any AutofillSecureVault)?,
+ credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging) {
+ self.secureVault = secureVault
+ self.credentialIdentityStoreManager = credentialIdentityStoreManager
+ }
+
+ func fetchCredential(for account: SecureVaultModels.WebsiteAccount) -> ASPasswordCredential {
+ let password = retrievePassword(for: account)
+
+ updateLastUsed(for: account)
+
+ return ASPasswordCredential(user: account.username ?? "", password: password)
+ }
+
+ func fetchCredential(for identity: ASPasswordCredentialIdentity) -> ASPasswordCredential? {
+ return fetchCredentialHelper(for: identity.user, recordIdentifier: identity.recordIdentifier)
+ }
+
+ @available(iOS 17.0, *)
+ func fetchCredential(for identity: ASCredentialIdentity) -> ASPasswordCredential? {
+ return fetchCredentialHelper(for: identity.user, recordIdentifier: identity.recordIdentifier)
+ }
+
+ // MARK: - Private
+
+ private func retrievePassword(for account: SecureVaultModels.WebsiteAccount) -> String {
+ guard let accountID = account.id, let accountIdInt = Int64(accountID), let credentials = retrieveCredentials(for: accountIdInt) else {
+ return ""
+ }
+
+ return credentials.password.flatMap { String(data: $0, encoding: .utf8) } ?? ""
+ }
+
+ private func fetchCredentialHelper(for user: String, recordIdentifier: String?) -> ASPasswordCredential? {
+ guard let recordIdentifier = recordIdentifier,
+ let accountIdInt = Int64(recordIdentifier),
+ let credentials = retrieveCredentials(for: accountIdInt) else {
+ return nil
+ }
+
+ let passwordCredential = ASPasswordCredential(user: user,
+ password: credentials.password.flatMap { String(data: $0, encoding: .utf8) } ?? "")
+
+ updateLastUsed(for: credentials.account)
+
+ return passwordCredential
+ }
+
+ private func retrieveCredentials(for accountId: Int64) -> SecureVaultModels.WebsiteCredentials? {
+ guard let vault = secureVault else { return nil }
+ do {
+ return try vault.websiteCredentialsFor(accountId: accountId)
+ } catch {
+ Pixel.fire(pixel: .secureVaultError, error: error)
+ return nil
+ }
+ }
+
+ private func updateLastUsed(for account: SecureVaultModels.WebsiteAccount) {
+ if let accountID = account.id, let accountIdInt = Int64(accountID), let vault = secureVault {
+ do {
+ try vault.updateLastUsedFor(accountId: accountIdInt)
+
+ Task {
+ if let domain = account.domain {
+ await credentialIdentityStoreManager.updateCredentialStore(for: domain)
+ }
+ }
+ } catch {
+ Pixel.fire(pixel: .secureVaultError, error: error)
+ }
+ }
+ }
+}
diff --git a/AutofillCredentialProvider/Info.plist b/AutofillCredentialProvider/Info.plist
new file mode 100644
index 0000000000..c00abcf6dc
--- /dev/null
+++ b/AutofillCredentialProvider/Info.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ DuckDuckGoGroupIdentifierPrefix
+ $(GROUP_ID_PREFIX)
+ NSExtension
+
+ NSExtensionAttributes
+
+ ASCredentialProviderViewControllerClass
+ $(PRODUCT_MODULE_NAME).CredentialProviderViewController
+ ASCredentialProviderExtensionShowsConfigurationUI
+
+ ASCredentialProviderExtensionCapabilities
+
+ ProvidesPasswords
+
+ ProvidesTextToInsert
+
+
+
+ NSExtensionPointIdentifier
+ com.apple.authentication-services-credential-provider-ui
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).CredentialProviderViewController
+
+ VAULT_APP_GROUP
+ $(AppIdentifierPrefix)$(VAULT_APP_GROUP)
+
+
diff --git a/AutofillCredentialProvider/bg.lproj/InfoPlist.strings b/AutofillCredentialProvider/bg.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/bg.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/bg.lproj/Localizable.strings b/AutofillCredentialProvider/bg.lproj/Localizable.strings
new file mode 100644
index 0000000000..82a8dc2b8c
--- /dev/null
+++ b/AutofillCredentialProvider/bg.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Затваряне";
+
+/* Done button title */
+"action.button.done" = "Готово";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Отваряне на DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Автоматичното попълване на пароли е активирано!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "устройство";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Отмени";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Отключете устройството, за да получите достъп до паролите";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Адрес на уебсайта";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Копиране на %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Адресът е копиран";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Бележките са копирани";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Паролата е копирана";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Потребителското име е копирано";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Скриване на паролата";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Последна актуализация %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Заглавие";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Бележки";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Парола";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Показване на паролата";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Потребителско име";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Паролите се съхраняват сигурно на Вашето устройство.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Все още няма запазени пароли";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Избиране на парола, която да бъде използвана за „%@“";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Търсене на пароли";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "за '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Няма резултати";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Предложения";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Пароли";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Задаване на парола на %@ за автоматично попълване на паролите за DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Изисква се парола на устройството";
+
diff --git a/AutofillCredentialProvider/cs.lproj/InfoPlist.strings b/AutofillCredentialProvider/cs.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/cs.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/cs.lproj/Localizable.strings b/AutofillCredentialProvider/cs.lproj/Localizable.strings
new file mode 100644
index 0000000000..230c19ae1c
--- /dev/null
+++ b/AutofillCredentialProvider/cs.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Zavřít";
+
+/* Done button title */
+"action.button.done" = "Hotovo";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Otevřít DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatické vyplňování hesel je zapnuté.";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "zařízení";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhonu";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPadu";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Zrušit";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Pro přístup k heslům zařízení odemkni";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL webové stránky";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopírovat %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adresa zkopírovaná";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Poznámky zkopírované";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Heslo zkopírované";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Uživatelské jméno zkopírované";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Skrýt heslo";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Poslední aktualizace %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Název";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Poznámky";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Heslo";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Zobrazit heslo";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Uživatelské jméno";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Hesla se bezpečně ukládají do tvého zařízení.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Zatím nemáš uložená žádná hesla";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Vyber si, které heslo použiješ pro účet %@";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Prohledat hesla";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "pro „%@“";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Žádné výsledky";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Navrhované";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Hesla";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Nastav si přístupový kód pro zařízení %@ a DuckDuckGo bude automaticky vyplňovat hesla.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Přístupový kód pro zařízení je povinný";
+
diff --git a/AutofillCredentialProvider/da.lproj/InfoPlist.strings b/AutofillCredentialProvider/da.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/da.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/da.lproj/Localizable.strings b/AutofillCredentialProvider/da.lproj/Localizable.strings
new file mode 100644
index 0000000000..49ea766df6
--- /dev/null
+++ b/AutofillCredentialProvider/da.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Luk";
+
+/* Done button title */
+"action.button.done" = "Færdig";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Åbn DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatisk udfyldning af adgangskoder er aktiveret!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "enhed";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Annullér";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Lås enheden op for at få adgang til adgangskoder";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL til webstedet";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopiér %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adresse kopieret";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Noter kopieret";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Adgangskode kopieret";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Brugernavn kopieret";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Skjul adgangskode";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Sidst opdateret %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Titel";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Noter";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Adgangskode";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Vis adgangskode";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Brugernavn";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Adgangskoder gemmes sikkert på din enhed.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Ingen adgangskoder gemt endnu";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Vælg en adgangskode til \"%@\"";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Søg adgangskoder";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "for '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Ingen resultater";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Foreslået";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Adgangskoder";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Opret en adgangskode på %@ for automatisk at udfylde dine DuckDuckGo-adgangskoder.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Adgangskode til enheden påkrævet";
+
diff --git a/AutofillCredentialProvider/de.lproj/InfoPlist.strings b/AutofillCredentialProvider/de.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/de.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/de.lproj/Localizable.strings b/AutofillCredentialProvider/de.lproj/Localizable.strings
new file mode 100644
index 0000000000..e36875c16b
--- /dev/null
+++ b/AutofillCredentialProvider/de.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Schließen";
+
+/* Done button title */
+"action.button.done" = "Fertig";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "DuckDuckGo öffnen";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatisches Ausfüllen von Passwörtern aktiviert!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "Gerät";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Abbrechen";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Gerät entsperren, um auf Passwörter zuzugreifen";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL der Website";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "%@ kopieren";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adresse kopiert";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Notizen kopiert";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Passwort kopiert";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Benutzername kopiert";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Passwort ausblenden";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Zuletzt aktualisiert %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Titel";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Notizen";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Passwort";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Passwort anzeigen";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Benutzername";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Passwörter werden sicher auf deinem Gerät gespeichert.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Noch keine Passwörter gespeichert";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Wähle ein Passwort, das du für „%@“ verwendest";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Passwörter suchen";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "für „%@“";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Keine Ergebnisse";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Vorgeschlagen";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Passwörter";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Lege einen Passcode für dein %@ fest, um deine DuckDuckGo-Passwörter automatisch auszufüllen.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Geräte-Passcode erforderlich";
+
diff --git a/AutofillCredentialProvider/el.lproj/InfoPlist.strings b/AutofillCredentialProvider/el.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/el.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/el.lproj/Localizable.strings b/AutofillCredentialProvider/el.lproj/Localizable.strings
new file mode 100644
index 0000000000..cc073f02b6
--- /dev/null
+++ b/AutofillCredentialProvider/el.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Κλείσιμο";
+
+/* Done button title */
+"action.button.done" = "Ολοκληρώθηκε";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Άνοιγμα του DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Η Αυτόματη συμπλήρωση κωδικών πρόσβασης ενεργοποιήθηκε!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "συσκευή";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Ακύρωση";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Ξεκλειδώστε τη συσκευή για πρόσβαση σε κωδικούς πρόσβασης";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Διεύθυνση URL ιστότοπου";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Αντιγραφή %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Η διεύθυνση αντιγράφηκε";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Οι σημειώσεις αντιγράφηκαν";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Ο κωδικός πρόσβασης αντιγράφηκε";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Το όνομα χρήστη αντιγράφηκε";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Απόκρυψη κωδικού πρόσβασης";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Τελευταία ενημέρωση %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Τίτλος";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Σημειώσεις";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Κωδικός πρόσβασης";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Εμφάνιση κωδικού πρόσβασης";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Όνομα χρήστη";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Οι κωδικοί πρόσβασης αποθηκεύονται με ασφάλεια στη συσκευή σας.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Δεν έχουν αποθηκευτεί ακόμα κωδικοί πρόσβασης";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Επιλέξτε έναν κωδικό πρόσβασης για χρήση στο «%@»";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Αναζήτηση κωδικών πρόσβασης";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "για '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Κανένα αποτέλεσμα";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Προτεινόμενο";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Κωδικός πρόσβασης";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Ορίστε έναν κωδικό πρόσβασης στο %@ για αυτόματη συμπλήρωση των κωδικών πρόσβασης DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Απαιτείται κωδικός πρόσβασης συσκευής";
+
diff --git a/AutofillCredentialProvider/es.lproj/InfoPlist.strings b/AutofillCredentialProvider/es.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/es.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/es.lproj/Localizable.strings b/AutofillCredentialProvider/es.lproj/Localizable.strings
new file mode 100644
index 0000000000..572db888b4
--- /dev/null
+++ b/AutofillCredentialProvider/es.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Cerrar";
+
+/* Done button title */
+"action.button.done" = "Hecho";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Abrir DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "¡La función de autocompletar contraseñas está activada!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "dispositivo";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Cancelar";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Desbloquear dispositivo para acceder a contraseñas";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL del sitio web";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Copiar %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Dirección copiada";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Notas copiadas";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Contraseña copiada";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Nombre de usuario copiado";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Ocultar contraseña";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Última actualización % @";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Título";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Notas";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Contraseña";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Mostrar contraseña";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Nombre de usuario";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Las contraseñas se almacenan de forma segura en tu dispositivo.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Aún no hay contraseñas guardadas";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Elige una contraseña para \"%@\"";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Buscar contraseñas";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "para '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Sin resultados";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Sugerencias";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Contraseñas";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Configura un código de acceso en %@ para autocompletar tus contraseñas de DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Se requiere código de acceso del dispositivo";
+
diff --git a/AutofillCredentialProvider/et.lproj/InfoPlist.strings b/AutofillCredentialProvider/et.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/et.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/et.lproj/Localizable.strings b/AutofillCredentialProvider/et.lproj/Localizable.strings
new file mode 100644
index 0000000000..0e60b0e2e6
--- /dev/null
+++ b/AutofillCredentialProvider/et.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Sulge";
+
+/* Done button title */
+"action.button.done" = "Valmis";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Ava DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Paroolide automaatne täitmine on aktiveeritud!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "seade";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Tühista";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Paroolidele juurdepääsuks ava seade";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Veebisaidi URL";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopeeri %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Aadress on kopeeritud";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Märkmed on kopeeritud";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Parool on kopeeritud";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Kasutajanimi on kopeeritud";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Peida parool";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Viimati uuendatud %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Pealkiri";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Märkused";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Parool";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Kuva parool";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Kasutajanimi";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Paroolid salvestatakse turvaliselt sinu seadmesse.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Paroole ei ole veel salvestatud";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Valige salasõna, mida kasutada \"%@\" jaoks";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Otsi paroole";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "otsinguga '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Tulemusi pole";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Soovitatud";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Paroolid";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "DuckDuckGo paroolide automaatseks täitmiseks määrake saidile %@ pääsukood.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Nõutav seadme pääsukood";
+
diff --git a/AutofillCredentialProvider/fi.lproj/InfoPlist.strings b/AutofillCredentialProvider/fi.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/fi.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/fi.lproj/Localizable.strings b/AutofillCredentialProvider/fi.lproj/Localizable.strings
new file mode 100644
index 0000000000..6bbcdd63c0
--- /dev/null
+++ b/AutofillCredentialProvider/fi.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Sulje";
+
+/* Done button title */
+"action.button.done" = "Valmis";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Avaa DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Salasanojen automaattinen täyttö on nyt käytössä!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "laitteelle";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhonelle";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPadille";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Peruuta";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Avaa laitteen lukitus päästäksesi salasanoihin";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Sivuston URL-osoite";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopioi %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Osoite kopioitu";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Muistiinpanot kopioitu";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Salasana kopioitu";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Käyttäjätunnus kopioitu";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Piilota salasana";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Viimeksi päivitetty %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Otsikko";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Huomautukset";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Salasana";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Näytä salasana";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Käyttäjätunnus";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Salasanat tallennetaan laitteellesi turvallisesti.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Salasanoja ei ole vielä tallennettu";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Valitse sivuston salasana: %@";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Etsi salasanoja";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "kohteelle '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Ei tuloksia";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Ehdotettu";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Salasanat";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Jos haluat, että DuckDuckGo täyttää salasanasi automaattisesti, määritä pääsykoodi laitteessa %@.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Edellyttää laitteen pääsykoodia";
+
diff --git a/AutofillCredentialProvider/fr.lproj/InfoPlist.strings b/AutofillCredentialProvider/fr.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/fr.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/fr.lproj/Localizable.strings b/AutofillCredentialProvider/fr.lproj/Localizable.strings
new file mode 100644
index 0000000000..8f01a4e036
--- /dev/null
+++ b/AutofillCredentialProvider/fr.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Fermer";
+
+/* Done button title */
+"action.button.done" = "Terminé";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Ouvrir DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Saisie automatique des mots de passe activée !";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "appareil";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Annuler";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Déverrouiller l'appareil pour accéder aux mots de passe";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL du site Web";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Copier %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adresse copiée";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Notes copiées";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Mot de passe copié";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Nom d'utilisateur copié";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Masquer le mot de passe";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Dernière modification : %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Titre";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Notes";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Mot de passe";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Afficher le mot de passe";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Nom d'utilisateur";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Les mots de passe sont stockés en toute sécurité sur votre appareil.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Aucun mot de passe n'a été enregistré";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Choisissez un mot de passe à utiliser pour « %@ »";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Rechercher un mot de passe";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "pour '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Aucun résultat";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Suggéré";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Mots de passe";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Définissez un code d'accès sur %@ pour saisir automatiquement vos mots de passe DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Code d'accès de l'appareil requis";
+
diff --git a/AutofillCredentialProvider/hr.lproj/InfoPlist.strings b/AutofillCredentialProvider/hr.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/hr.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/hr.lproj/Localizable.strings b/AutofillCredentialProvider/hr.lproj/Localizable.strings
new file mode 100644
index 0000000000..38cc9387d7
--- /dev/null
+++ b/AutofillCredentialProvider/hr.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Zatvori";
+
+/* Done button title */
+"action.button.done" = "Gotovo";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Otvori DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatsko popunjavanje lozinki je aktivirano!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "uređaj";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Otkaži";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Otključaj uređaj za pristup lozinkama";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL web lokacije";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopiraj %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adresa je kopirana";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Bilješke su kopirane";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Lozinka je kopirana";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Korisničko ime je kopirano";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Sakrij lozinku";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Posljednje ažuriranje: %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Naslov";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Bilješke";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Lozinka";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Pokaži lozinku";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Korisničko ime";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Lozinke su sigurno pohranjene na tvom uređaju.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Još nema spremljenih lozinki";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Odaberi lozinku koju ćeš koristiti za \"%@\"";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Pretraživanje lozinki";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "za '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Nema rezultata";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Predloženo";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Lozinke";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Postavi šifru na uređaju %@ kako bi se tvoje DuckDuckGo lozinke popunjavale automatski.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Potrebna je šifra uređaja.";
+
diff --git a/AutofillCredentialProvider/hu.lproj/InfoPlist.strings b/AutofillCredentialProvider/hu.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/hu.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/hu.lproj/Localizable.strings b/AutofillCredentialProvider/hu.lproj/Localizable.strings
new file mode 100644
index 0000000000..c21a3bb9fb
--- /dev/null
+++ b/AutofillCredentialProvider/hu.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Bezárás";
+
+/* Done button title */
+"action.button.done" = "Kész";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Nyisd meg a DuckDuckGo-alkalmazást";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Jelszavak automatikus kitöltése aktiválva!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "eszközön";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone készüléken";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad készüléken";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Mégsem";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Oldd fel az eszközt a jelszavakhoz való hozzáféréshez";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Webhely URL-címe";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "%@ másolása";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Cím másolva";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Megjegyzések másolva";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Jelszó másolva";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Felhasználónév másolva";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Jelszó elrejtése";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Utolsó frissítés: %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Cím";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Megjegyzések";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Jelszó";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Jelszó megjelenítése";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Felhasználónév";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "A jelszavakat az eszközöd biztonságosan tárolja.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Még nincsenek mentett jelszavak";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Válassz egy jelszót ehhez: \"%@\"";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Jelszavak keresése";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "erre: „%@“";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Nincs találat";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Javasolt";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Jelszavak";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "A DuckDuckGo-jelszavak automatikus kitöltéséhez állíts be egy jelkódot itt: %@.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Meg kell adni az eszköz jelkódját";
+
diff --git a/AutofillCredentialProvider/it.lproj/InfoPlist.strings b/AutofillCredentialProvider/it.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/it.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/it.lproj/Localizable.strings b/AutofillCredentialProvider/it.lproj/Localizable.strings
new file mode 100644
index 0000000000..5b97f6c41c
--- /dev/null
+++ b/AutofillCredentialProvider/it.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Chiudi";
+
+/* Done button title */
+"action.button.done" = "Fatto";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Apri DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Compilazione automatica delle password attivata!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "dispositivo";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Annulla";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Sblocca il dispositivo per accedere alle password";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL del sito Web";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Copia %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Indirizzo copiato";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Appunti copiati";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Password copiata";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Nome utente copiato";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Nascondi password";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Ultimo aggiornamento %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Titolo";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Note";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Password";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Mostra password";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Nome utente";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Le password sono archiviate in modo sicuro sul tuo dispositivo.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Nessuna password ancora salvata";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Scegli una password da usare per \"%@\"";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Cerca password";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "per \"%@\"";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Nessun risultato";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Suggerimenti";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Password";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Imposta un codice di accesso su %@ per inserire automaticamente le tue password DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "È necessario il codice di accesso del dispositivo";
+
diff --git a/AutofillCredentialProvider/lt.lproj/InfoPlist.strings b/AutofillCredentialProvider/lt.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/lt.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/lt.lproj/Localizable.strings b/AutofillCredentialProvider/lt.lproj/Localizable.strings
new file mode 100644
index 0000000000..9867833bfd
--- /dev/null
+++ b/AutofillCredentialProvider/lt.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Uždaryti";
+
+/* Done button title */
+"action.button.done" = "Atlikta";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Atidaryti „DuckDuckGo“";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatinis slaptažodžių pildymas įjungtas!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "įrenginys";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Atšaukti";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Atrakinkite įrenginį, kad galėtumėte pasiekti slaptažodžius";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Svetainės URL";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopijuoti %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adresas nukopijuotas";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Pastabos nukopijuotos";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Slaptažodis nukopijuotas";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Naudotojo vardas nukopijuotas";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Paslėpti slaptažodį";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Paskutinį kartą atnaujinta %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Pavadinimas";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Pastabos";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Slaptažodis";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Rodyti slaptažodį";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Naudotojo vardas";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Slaptažodžiai saugiai saugomi jūsų įrenginyje.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Dar nėra išsaugotų slaptažodžių";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Pasirinkite slaptažodį, kurį naudosite „%@“";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Ieškoti slaptažodžių";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "„%@“";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Rezultatų nerasta";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Siūloma";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Slaptažodžiai";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Nustatykite %@ prieigos kodą, kad automatiškai užpildytumėte „DuckDuckGo“ slaptažodžius.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Reikalingas įrenginio kodas";
+
diff --git a/AutofillCredentialProvider/lv.lproj/InfoPlist.strings b/AutofillCredentialProvider/lv.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/lv.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/lv.lproj/Localizable.strings b/AutofillCredentialProvider/lv.lproj/Localizable.strings
new file mode 100644
index 0000000000..5c9150e72f
--- /dev/null
+++ b/AutofillCredentialProvider/lv.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Aizvērt";
+
+/* Done button title */
+"action.button.done" = "Gatavs";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Atvērt DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Paroļu automātiskā aizpildīšana ir aktivizēta!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "ierīcē";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Atcelt";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Atbloķē ierīci, lai piekļūtu parolēm";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Tīmekļa vietnes URL";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopēt %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adrese nokopēta";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Piezīmes nokopētas";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Parole nokopēta";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Lietotājvārds nokopēts";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Paslēpt paroli";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Pēdējoreiz atjaunināts %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Nosaukums";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Piezīmes";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Parole";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Rādīt paroli";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Lietotājvārds";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Paroles tiek droši glabātas tavā ierīcē.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Vēl nav saglabāta neviena parole";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Izvēlies paroli, ko izmantot \"%@\"";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Meklēt paroles";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "meklējumam \"%@\"";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Nav rezultātu";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Ieteikts";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Paroles";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Iestati piekļuves kodu %@, lai automātiski aizpildītu savas DuckDuckGo paroles.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Nepieciešams ierīces piekļuves kods";
+
diff --git a/AutofillCredentialProvider/nb.lproj/InfoPlist.strings b/AutofillCredentialProvider/nb.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/nb.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/nb.lproj/Localizable.strings b/AutofillCredentialProvider/nb.lproj/Localizable.strings
new file mode 100644
index 0000000000..858f84a0d9
--- /dev/null
+++ b/AutofillCredentialProvider/nb.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Lukk";
+
+/* Done button title */
+"action.button.done" = "Ferdig";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Åpne DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatisk fylling av passord er aktivert!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "enhet";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Avbryt";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Lås opp enheten for å få tilgang til passord";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL-adresse til nettsted";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopier %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adressen er kopiert";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Notatene er kopiert";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Passordet er kopiert";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Brukernavnet er kopiert";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Skjul passord";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Sist oppdatert %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Tittel";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Notater";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Passord";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Vis passord";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Brukernavn";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Passord lagres på enheten din på en sikker måte.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Ingen passord er lagret ennå";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Velg et passord du vil bruke til «%@»";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Søk i passord";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "for «%@»";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Ingen resultater";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Forslag";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Passord";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Angi en kode på %@ for å fylle inn DuckDuckGo-passordene dine automatisk.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Enhetens kode kreves";
+
diff --git a/AutofillCredentialProvider/nl.lproj/InfoPlist.strings b/AutofillCredentialProvider/nl.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/nl.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/nl.lproj/Localizable.strings b/AutofillCredentialProvider/nl.lproj/Localizable.strings
new file mode 100644
index 0000000000..5737a0c75a
--- /dev/null
+++ b/AutofillCredentialProvider/nl.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Sluiten";
+
+/* Done button title */
+"action.button.done" = "Klaar";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "DuckDuckGo openen";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatisch invullen van wachtwoorden geactiveerd!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "apparaat";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Annuleren";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Ontgrendel het apparaat om toegang te krijgen tot wachtwoorden";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL van de website";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "%@ kopiëren";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adres gekopieerd";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Opmerkingen gekopieerd";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Wachtwoord gekopieerd";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Gebruikersnaam gekopieerd";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Wachtwoord verbergen";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Laatst bijgewerkt op %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Titel";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Opmerkingen";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Wachtwoord";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Wachtwoord weergeven";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Gebruikersnaam";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Wachtwoorden worden veilig opgeslagen op je apparaat.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Nog geen wachtwoorden opgeslagen";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Kies een wachtwoord voor '%@'";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Wachtwoorden zoeken";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "voor '%@'";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Geen resultaten";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Aanbevolen";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Wachtwoorden";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Stel een toegangscode in op %@ om je DuckDuckGo-wachtwoorden automatisch in te vullen.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Toegangscode voor apparaat vereist";
+
diff --git a/AutofillCredentialProvider/pl.lproj/InfoPlist.strings b/AutofillCredentialProvider/pl.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/pl.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/pl.lproj/Localizable.strings b/AutofillCredentialProvider/pl.lproj/Localizable.strings
new file mode 100644
index 0000000000..b2198855c4
--- /dev/null
+++ b/AutofillCredentialProvider/pl.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Zamknij";
+
+/* Done button title */
+"action.button.done" = "Gotowe";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Otwórz DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatyczne uzupełnianie haseł zostało aktywowane!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "tym urządzeniu";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "tym telefonie iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "tym iPadzie";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Anuluj";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Odblokuj urządzenie, aby uzyskać dostęp do haseł";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Adres URL witryny";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopiuj %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Skopiowano adres";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Skopiowano notatki";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Skopiowano hasło";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Skopiowano nazwę użytkownika";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Ukryj hasło";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Ostatnia aktualizacja %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Tytuł";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Uwagi";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Hasło";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Pokaż hasło";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Nazwa użytkownika";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Hasła są bezpiecznie przechowywane na Twoim urządzeniu.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Nie zapisano jeszcze żadnych haseł";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Wybierz hasło do wykorzystania z „%@”";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Wyszukaj hasła";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "dla frazy: %@";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Brak wyników";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Sugerowane";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Hasła";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Ustaw kod dostępu na urządzeniu %@, aby automatycznie uzupełniać hasła DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Wymagany kod dostępu urządzenia";
+
diff --git a/AutofillCredentialProvider/pt.lproj/InfoPlist.strings b/AutofillCredentialProvider/pt.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/pt.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/pt.lproj/Localizable.strings b/AutofillCredentialProvider/pt.lproj/Localizable.strings
new file mode 100644
index 0000000000..6d2929f0ee
--- /dev/null
+++ b/AutofillCredentialProvider/pt.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Fechar";
+
+/* Done button title */
+"action.button.done" = "Feito";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Abrir DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Preenchimento automático de palavras-passe ativado!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "dispositivo";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Cancelar";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Desbloquear dispositivo para aceder às palavras-passe";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL do site";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Copiar %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Endereço copiado";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Notas copiadas";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Palavra-passe copiada";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Nome de utilizador copiado";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Ocultar palavra-passe";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Última atualização em %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Título";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Notas";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Palavra-passe";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Mostrar palavra-passe";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Nome de utilizador";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "As palavras-passe são armazenadas com segurança no teu dispositivo.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Ainda não há palavras-passe guardadas";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Escolhe uma palavra-passe para usar em \"%@\"";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Pesquisar palavras-passe";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "para \"%@\"";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Sem resultados";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Sugerido";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Palavras-passe";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Define um código de acesso em %@ para preencher automaticamente as tuas palavras-passe DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Código de acesso do dispositivo necessário";
+
diff --git a/AutofillCredentialProvider/ro.lproj/InfoPlist.strings b/AutofillCredentialProvider/ro.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/ro.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/ro.lproj/Localizable.strings b/AutofillCredentialProvider/ro.lproj/Localizable.strings
new file mode 100644
index 0000000000..e90eb1030d
--- /dev/null
+++ b/AutofillCredentialProvider/ro.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Închidere";
+
+/* Done button title */
+"action.button.done" = "Terminat";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Deschide DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Completarea automată a parolelor a fost activată!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "dispozitiv";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Renunță";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Deblochează dispozitivul pentru a accesa parolele";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL-ul site-ului";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Copiază %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adresă copiată";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Note copiate";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Parolă copiată";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Numele de utilizator a fost copiat";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Ascunde parola";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Ultima actualizare la %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Titlu";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Note";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Parolă";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Arată parola";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Nume utilizator";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Parolele sunt stocate în siguranță pe dispozitivul tău.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Nici o parolă nu a fost salvată încă";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Alege o parolă pe care să o utilizezi pentru „%@”";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Caută parole";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "pentru „%@”";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Niciun rezultat";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Sugerat";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Parole";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Setează o parolă pe %@ pentru a-ți completa automat parolele DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Codul de acces al dispozitivului este necesar";
+
diff --git a/AutofillCredentialProvider/ru.lproj/InfoPlist.strings b/AutofillCredentialProvider/ru.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/ru.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/ru.lproj/Localizable.strings b/AutofillCredentialProvider/ru.lproj/Localizable.strings
new file mode 100644
index 0000000000..823459918c
--- /dev/null
+++ b/AutofillCredentialProvider/ru.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Закрыть";
+
+/* Done button title */
+"action.button.done" = "Готово";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Открыть DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Автозаполнение паролей включено!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "устройстве";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Отменить";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Разблокируйте устройство, чтобы получить доступ к паролям";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Адрес сайта";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Копировать %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Адрес скопирован";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Примечания скопированы";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Пароль скопирован";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Имя пользователя скопировано!";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Скрыть пароль";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Последнее обновление: %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Название";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Примечания";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Пароль";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Показать пароль";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Имя пользователя";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Пароли надежно защищены и хранятся на вашем устройстве.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Сохраненных паролей пока нет";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Выберите пароль для сайта %@";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Найти пароль";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "по запросу «%@»";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Нет результатов";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Рекомендации";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Пароли";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Задайте код доступа, и %@ будет заполнять пароли из DuckDuckGo автоматически.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Требуется код доступа к устройству";
+
diff --git a/AutofillCredentialProvider/sk.lproj/InfoPlist.strings b/AutofillCredentialProvider/sk.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/sk.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/sk.lproj/Localizable.strings b/AutofillCredentialProvider/sk.lproj/Localizable.strings
new file mode 100644
index 0000000000..c704caedba
--- /dev/null
+++ b/AutofillCredentialProvider/sk.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Zatvoriť";
+
+/* Done button title */
+"action.button.done" = "Hotovo";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Otvoriť DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatické dopĺňanie hesiel je aktivované!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "zariadenie";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Zrušiť";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Ak chcete získať prístup k heslám, odomknite zariadenie";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Adresa URL webových stránok";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopírovať %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adresa bola skopírovaná";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Poznámky boli skopírované";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Heslo bolo skopírované";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Meno používateľa bolo skopírované";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Skryť heslo";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Naposledy aktualizované %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Názov";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Poznámky";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Heslo";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Zobraziť heslo";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Používateľské meno";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Heslá sú bezpečne uložené vo vašom zariadení.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Zatiaľ nie sú uložené žiadne heslá";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Vyber si heslo, ktoré chceš použiť pre „%@“";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Vyhľadávanie hesiel";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "pre „%@”";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Žiadne výsledky";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Navrhované";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Heslo";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Nastav prístupový kód na %@ na automatické vypĺňanie tvojich hesiel DuckDuckGo.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Vyžaduje sa prístupový kód zariadenia.";
+
diff --git a/AutofillCredentialProvider/sl.lproj/InfoPlist.strings b/AutofillCredentialProvider/sl.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/sl.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/sl.lproj/Localizable.strings b/AutofillCredentialProvider/sl.lproj/Localizable.strings
new file mode 100644
index 0000000000..2ccec47f15
--- /dev/null
+++ b/AutofillCredentialProvider/sl.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Zapri";
+
+/* Done button title */
+"action.button.done" = "Končano";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Odpri DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Samodejno izpolnjevanje gesel je aktivirano.";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "naprava";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Prekliči";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Če želite dostopati do gesel, odklenite napravo";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "URL spletnega mesta";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopiraj %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Naslov je bil kopiran";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Opombe so kopirane";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Geslo je bilo kopirano";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Uporabniško ime je bilo kopirano";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Skrij geslo";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Zadnja posodobitev %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Naslov";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Opombe";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Geslo";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Prikaži geslo";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Uporabniško ime";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Gesla so varno shranjena v vaši napravi.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Nobeno geslo še ni shranjeno";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Izberite geslo za »%@«";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Iskanje gesel";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "za »%@«";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Ni rezultatov";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Predlagano";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Gesla";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Za samodejno izpolnjevanje gesel DuckDuckGo nastavite kodo za dostop v svoji napravi (%@).";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Zahtevana je koda za dostop do naprave";
+
diff --git a/AutofillCredentialProvider/sv.lproj/InfoPlist.strings b/AutofillCredentialProvider/sv.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/sv.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/sv.lproj/Localizable.strings b/AutofillCredentialProvider/sv.lproj/Localizable.strings
new file mode 100644
index 0000000000..04f1b6425c
--- /dev/null
+++ b/AutofillCredentialProvider/sv.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Stäng";
+
+/* Done button title */
+"action.button.done" = "Klart";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "Öppna DuckDuckGo";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Automatisk ifyllning av lösenord har aktiverats!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "enhet";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "Avbryt";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Lås upp enheten för att komma åt lösenord";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Webbplats-URL";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "Kopiera %@";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adress kopierad";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Anteckningar kopierade";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Lösenord kopierat";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Användarnamn kopierat";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Dölj lösenord";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Uppdaterades senast %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Rubrik";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Anteckningar";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Lösenord";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Visa lösenord";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Användarnamn";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Lösenord lagras säkert på din enhet.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Inga lösenord sparade ännu";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "Välj ett lösenord att använda för %@";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Sök lösenord";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "för ”%@”";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Inga resultat";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Förslag";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Lösenord";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "Ställ in en lösenkod på %@ för att automatiskt fylla i dina DuckDuckGo-lösenord.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Enhetens lösenkod krävs";
+
diff --git a/AutofillCredentialProvider/tr.lproj/InfoPlist.strings b/AutofillCredentialProvider/tr.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..9809aa19f5
--- /dev/null
+++ b/AutofillCredentialProvider/tr.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "AutofillCredentialProvider";
+
+/* Bundle name */
+"CFBundleName" = "AutofillCredentialProvider";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2024 DuckDuckGo. All rights reserved.";
+
diff --git a/AutofillCredentialProvider/tr.lproj/Localizable.strings b/AutofillCredentialProvider/tr.lproj/Localizable.strings
new file mode 100644
index 0000000000..1bf39ce8dc
--- /dev/null
+++ b/AutofillCredentialProvider/tr.lproj/Localizable.strings
@@ -0,0 +1,96 @@
+/* Close button title */
+"action.button.close" = "Kapat";
+
+/* Done button title */
+"action.button.done" = "Bitti";
+
+/* Title of button to launch the DuckDuckGo app */
+"credential.provider.activated.button" = "DuckDuckGo'yu aç";
+
+/* The title of the screen confirming DuckDuckGo can now be used for autofilling passwords */
+"credential.provider.activated.title" = "Şifreleri otomatik doldurma etkinleştirildi!";
+
+/* Default string used if users device is not iPhone or iPad */
+"credential.provider.device.type.default" = "cihaz";
+
+/* Device type is iPhone */
+"credential.provider.device.type.iphone" = "iPhone";
+
+/* Device type is iPad */
+"credential.provider.device.type.pad" = "iPad";
+
+/* Cancel button for auth when opening login list */
+"credential.provider.list.auth.cancel" = "İptal";
+
+/* Reason for auth when opening screen with list of saved passwords */
+"credential.provider.list.auth.reason" = "Şifrelere erişmek için cihazın kilidini açın";
+
+/* Address label for login details on autofill */
+"credential.provider.list.details.address" = "Web sitesi URL'si";
+
+/* Menu item text for copying autofill login details */
+"credential.provider.list.details.copy-prompt" = "%@ Ögesini Kopyala";
+
+/* Title for toast when copying address */
+"credential.provider.list.details.copy-toast.address-copied" = "Adres kopyalandı";
+
+/* Title for toast when copying notes */
+"credential.provider.list.details.copy-toast.notes-copied" = "Notlar kopyalandı";
+
+/* Title for toast when copying password */
+"credential.provider.list.details.copy-toast.password-copied" = "Şifre kopyalandı";
+
+/* Title for toast when copying username */
+"credential.provider.list.details.copy-toast.username-copied" = "Kullanıcı adı kopyalandı";
+
+/* Accessibility title for a Hide Password button replacing displayed password with ***** */
+"credential.provider.list.details.hide-password" = "Parolayı gizle";
+
+/* Message displaying when the login was last updated */
+"credential.provider.list.details.last-updated" = "Son güncelleme %@";
+
+/* Login name label for login details on autofill */
+"credential.provider.list.details.login-name" = "Title";
+
+/* Notes label for login details on autofill */
+"credential.provider.list.details.notes" = "Notlar";
+
+/* Password label for login details on autofill */
+"credential.provider.list.details.password" = "Parola";
+
+/* Accessibility title for a Show Password button displaying actial password instead of ***** */
+"credential.provider.list.details.show-password" = "Parolayı göster";
+
+/* Username label for login details on autofill */
+"credential.provider.list.details.username" = "Kullanıcı adı";
+
+/* Footer label displayed below table section with option to enable autofill */
+"credential.provider.list.empty-view.footer" = "Şifreler cihazınızda güvenli bir şekilde saklanır.";
+
+/* Title for view displayed when autofill has no items */
+"credential.provider.list.empty-view.title" = "Henüz şifre kaydedilmedi";
+
+/* Prompt above the title for screen listing autofill logins, example: Choose a password to use for "website.com" */
+"credential.provider.list.prompt" = "\"%@\" için kullanılacak bir şifre seçin";
+
+/* Placeholder for search field on autofill login listing */
+"credential.provider.list.search-placeholder" = "Şifreleri ara";
+
+/* Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle) */
+"credential.provider.list.search.no-results.subtitle" = "\"%@\" için";
+
+/* Title displayed when there are no results on Autofill search */
+"credential.provider.list.search.no-results.title" = "Sonuç yok";
+
+/* Section title for group of suggested saved logins */
+"credential.provider.list.suggested" = "Önerilen";
+
+/* Title for screen listing autofill logins */
+"credential.provider.list.title" = "Şifreler";
+
+/* Message for alert when device authentication is not set, where %@ is iPhone|iPad|device */
+"credential.provider.no-device-auth-set.message" = "DuckDuckGo şifrelerinizi otomatik olarak doldurmak için %@ üzerinde bir parola belirleyin.";
+
+/* Title for alert when device authentication is not set */
+"credential.provider.no-device-auth-set.title" = "Cihaz Parolası Gerekli";
+
diff --git a/Configuration/Configuration-Alpha.xcconfig b/Configuration/Configuration-Alpha.xcconfig
index 73ad56cfdc..ba21ec9797 100644
--- a/Configuration/Configuration-Alpha.xcconfig
+++ b/Configuration/Configuration-Alpha.xcconfig
@@ -30,5 +30,8 @@ GROUP_ID_PREFIX = group.com.duckduckgo.alpha
// The keychain access group for subscriptions
SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions.alpha
+// The keychain access group for secure vault
+VAULT_APP_GROUP = com.duckduckgo.vault.alpha
+
// Prevents asserts from crashing alpha TF builds
OTHER_SWIFT_FLAGS[config=Alpha][arch=*][sdk=*] = $(inherited) -assert-config Release
diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig
index 2f435d72f7..fde561fa40 100644
--- a/Configuration/Version.xcconfig
+++ b/Configuration/Version.xcconfig
@@ -1 +1 @@
-MARKETING_VERSION = 7.148.0
+MARKETING_VERSION = 7.149.1
diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift
index 3d1a36860f..ce23ec7e8a 100644
--- a/Core/AppPrivacyConfigurationDataProvider.swift
+++ b/Core/AppPrivacyConfigurationDataProvider.swift
@@ -23,8 +23,8 @@ import BrowserServicesKit
final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider {
public struct Constants {
- public static let embeddedDataETag = "\"a913df909743eadb5bd381c7fddf4902\""
- public static let embeddedDataSHA = "d1fb789ab4def06b6cf359d5e9971c840668ab5307f5690bc1483bb2266a2558"
+ public static let embeddedDataETag = "\"03c27339b8dd9a474cc2bafb84ba8012\""
+ public static let embeddedDataSHA = "783c4cccf30cc0b4fe3caea212c9e1dca3b276b859d07360fa08b586f006438f"
}
public var embeddedDataEtag: String {
diff --git a/Core/AppTrackerDataSetProvider.swift b/Core/AppTrackerDataSetProvider.swift
index 1ad2d9fb89..4e73164f0a 100644
--- a/Core/AppTrackerDataSetProvider.swift
+++ b/Core/AppTrackerDataSetProvider.swift
@@ -23,8 +23,8 @@ import BrowserServicesKit
final public class AppTrackerDataSetProvider: EmbeddedDataProvider {
public struct Constants {
- public static let embeddedDataETag = "\"7af63a1c5812c9200d3120f5dd598993\""
- public static let embeddedDataSHA = "ef3eb8b998faa201192a190cff3fe0fc228cf2212be44271036dbd63ade5ddcf"
+ public static let embeddedDataETag = "\"382541021cbc55b218714db779561a51\""
+ public static let embeddedDataSHA = "5e8293a4c3e3ea5217e8e39db0c8525de4647ec78b102cff292ab5fa213578aa"
}
public var embeddedDataEtag: String {
diff --git a/DuckDuckGo/AutofillInterfaceEmailTruncator.swift b/Core/AutofillInterfaceEmailTruncator.swift
similarity index 91%
rename from DuckDuckGo/AutofillInterfaceEmailTruncator.swift
rename to Core/AutofillInterfaceEmailTruncator.swift
index 2eb28faa1b..41fb28342c 100644
--- a/DuckDuckGo/AutofillInterfaceEmailTruncator.swift
+++ b/Core/AutofillInterfaceEmailTruncator.swift
@@ -19,8 +19,8 @@
import Foundation
-struct AutofillInterfaceEmailTruncator {
- static func truncateEmail(_ email: String, maxLength: Int) -> String {
+public struct AutofillInterfaceEmailTruncator {
+ public static func truncateEmail(_ email: String, maxLength: Int) -> String {
let emailComponents = email.components(separatedBy: "@")
if emailComponents.count > 1 && email.count > maxLength {
let ellipsis = "..."
diff --git a/DuckDuckGo/AutofillLoginListItemViewModel.swift b/Core/AutofillLoginItem.swift
similarity index 57%
rename from DuckDuckGo/AutofillLoginListItemViewModel.swift
rename to Core/AutofillLoginItem.swift
index 8bbc5c3f6f..2eb5e3b71a 100644
--- a/DuckDuckGo/AutofillLoginListItemViewModel.swift
+++ b/Core/AutofillLoginItem.swift
@@ -1,8 +1,8 @@
//
-// AutofillLoginListItemViewModel.swift
+// AutofillLoginItem.swift
// DuckDuckGo
//
-// Copyright © 2022 DuckDuckGo. All rights reserved.
+// 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.
@@ -19,40 +19,44 @@
import Foundation
import BrowserServicesKit
-import UIKit
import Common
-final class AutofillLoginListItemViewModel: Identifiable, Hashable {
-
- var preferredFaviconLetters: String {
+public struct AutofillLoginItem: Identifiable, Hashable {
+
+ public let id = UUID()
+ public let account: SecureVaultModels.WebsiteAccount
+ public let title: String
+ public let subtitle: String
+
+ public var preferredFaviconLetters: String {
let accountName = self.account.name(tld: tld, autofillDomainNameUrlMatcher: urlMatcher)
let accountTitle = (account.title?.isEmpty == false) ? account.title! : "#"
return tld.eTLDplus1(accountName) ?? accountTitle
}
-
- let account: SecureVaultModels.WebsiteAccount
- let title: String
- let subtitle: String
- let id = UUID()
- let tld: TLD
- let urlMatcher: AutofillDomainNameUrlMatcher
-
- internal init(account: SecureVaultModels.WebsiteAccount,
- tld: TLD,
- autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher,
- autofillDomainNameUrlSort: AutofillDomainNameUrlSort) {
+
+ private let tld: TLD
+ private let urlMatcher: AutofillDomainNameUrlMatcher
+
+ public init(account: SecureVaultModels.WebsiteAccount,
+ tld: TLD,
+ autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher,
+ autofillDomainNameUrlSort: AutofillDomainNameUrlSort) {
self.account = account
self.tld = tld
+ self.urlMatcher = autofillDomainNameUrlMatcher
self.title = account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher)
self.subtitle = account.username ?? ""
- self.urlMatcher = autofillDomainNameUrlMatcher
}
-
- static func == (lhs: AutofillLoginListItemViewModel, rhs: AutofillLoginListItemViewModel) -> Bool {
+
+ public static func == (lhs: AutofillLoginItem, rhs: AutofillLoginItem) -> Bool {
lhs.account.id == rhs.account.id
}
-
- func hash(into hasher: inout Hasher) {
+
+ static func < (lhs: AutofillLoginItem, rhs: AutofillLoginItem) -> Bool {
+ lhs.title < rhs.title
+ }
+
+ public func hash(into hasher: inout Hasher) {
hasher.combine(account.id)
}
}
diff --git a/Core/AutofillLoginListSectionType.swift b/Core/AutofillLoginListSectionType.swift
new file mode 100644
index 0000000000..5c05d2f0e3
--- /dev/null
+++ b/Core/AutofillLoginListSectionType.swift
@@ -0,0 +1,44 @@
+//
+// AutofillLoginListSectionType.swift
+// DuckDuckGo
+//
+// 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
+
+public enum AutofillLoginListSectionType: Comparable {
+
+ case enableAutofill
+ case suggestions(title: String, items: [AutofillLoginItem])
+ case credentials(title: String, items: [AutofillLoginItem])
+
+ public static func < (lhs: AutofillLoginListSectionType, rhs: AutofillLoginListSectionType) -> Bool {
+ if case .credentials(let leftTitle, _) = lhs,
+ case .credentials(let rightTitle, _) = rhs {
+ if leftTitle == miscSectionHeading {
+ return false
+ } else if rightTitle == miscSectionHeading {
+ return true
+ }
+
+ return leftTitle.localizedCaseInsensitiveCompare(rightTitle) == .orderedAscending
+ }
+ return true
+ }
+
+ public static let miscSectionHeading = "#"
+
+}
diff --git a/Core/AutofillLoginListSorting.swift b/Core/AutofillLoginListSorting.swift
new file mode 100644
index 0000000000..a58407efbe
--- /dev/null
+++ b/Core/AutofillLoginListSorting.swift
@@ -0,0 +1,65 @@
+//
+// AutofillLoginListSorting.swift
+// DuckDuckGo
+//
+// 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 BrowserServicesKit
+import Common
+
+public extension Array where Element == SecureVaultModels.WebsiteAccount {
+
+ func groupedByFirstLetter(tld: TLD,
+ autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher,
+ autofillDomainNameUrlSort: AutofillDomainNameUrlSort)
+ -> [String: [AutofillLoginItem]] {
+ reduce(into: [String: [AutofillLoginItem]]()) { result, account in
+
+ // Unfortunetly, folding doesn't produce perfect results despite respecting the system locale
+ // E.g. Romainian should treat letters with diacritics as seperate letters, but folding doesn't
+ // Apple's own apps (e.g. contacts) seem to suffer from the same problem
+ let key: String
+ if let firstChar = autofillDomainNameUrlSort.firstCharacterForGrouping(account, tld: tld),
+ let deDistinctionedChar = String(firstChar).folding(options: [.diacriticInsensitive, .caseInsensitive], locale: nil).first,
+ deDistinctionedChar.isLetter {
+
+ key = String(deDistinctionedChar)
+ } else {
+ key = AutofillLoginListSectionType.miscSectionHeading
+ }
+
+ return result[key, default: []].append(AutofillLoginItem(account: account,
+ tld: tld,
+ autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher,
+ autofillDomainNameUrlSort: autofillDomainNameUrlSort))
+ }
+ }
+}
+
+public extension Dictionary where Key == String, Value == [AutofillLoginItem] {
+
+ func sortedIntoSections(_ autofillDomainNameUrlSort: AutofillDomainNameUrlSort,
+ tld: TLD) -> [AutofillLoginListSectionType] {
+ map { dictionaryItem -> AutofillLoginListSectionType in
+ let sortedGroup = dictionaryItem.value.sorted(by: {
+ autofillDomainNameUrlSort.compareAccountsForSortingAutofill(lhs: $0.account, rhs: $1.account, tld: tld) == .orderedAscending
+ })
+ return AutofillLoginListSectionType.credentials(title: dictionaryItem.key,
+ items: sortedGroup)
+ }.sorted()
+ }
+}
diff --git a/Core/ContentBlocking.swift b/Core/ContentBlocking.swift
index ed3f02d1aa..1a9c2cd7ba 100644
--- a/Core/ContentBlocking.swift
+++ b/Core/ContentBlocking.swift
@@ -116,9 +116,6 @@ public final class ContentBlocking {
domainEvent = .contentBlockingCompilationFailed(listType: listType, component: component)
- case .contentBlockingCompilationTime:
- domainEvent = .contentBlockingCompilationTime
-
case .contentBlockingLookupRulesSucceeded:
domainEvent = .contentBlockingLookupRulesSucceeded
@@ -130,6 +127,10 @@ public final class ContentBlocking {
case .contentBlockingLRCMissing:
domainEvent = .contentBlockingLRCMissing
+
+ case .contentBlockingCompilationTaskPerformance(let retryCount, let timeBucketAggregation):
+ domainEvent = .contentBlockingCompilationTaskPerformance(iterationCount: retryCount,
+ timeBucketAggregation: Pixel.Event.CompileTimeBucketAggregation(number: timeBucketAggregation))
}
if let error = error {
diff --git a/Core/DefaultVariantManager.swift b/Core/DefaultVariantManager.swift
index 76f0f8475a..8e6748dd41 100644
--- a/Core/DefaultVariantManager.swift
+++ b/Core/DefaultVariantManager.swift
@@ -27,9 +27,8 @@ extension FeatureName {
// Define your feature e.g.:
// public static let experimentalFeature = FeatureName(rawValue: "experimentalFeature")
- public static let newOnboardingIntro = FeatureName(rawValue: "newOnboardingIntro")
- public static let newOnboardingIntroHighlights = FeatureName(rawValue: "newOnboardingIntroHighlights")
- public static let contextualDaxDialogs = FeatureName(rawValue: "contextualDaxDialogs")
+ public static let addToDockIntro = FeatureName(rawValue: "addToDockIntro")
+ public static let addToDockContextual = FeatureName(rawValue: "addToDockContextual")
}
public struct VariantIOS: Variant {
@@ -58,9 +57,9 @@ public struct VariantIOS: Variant {
VariantIOS(name: "sd", weight: doNotAllocate, isIncluded: When.always, features: []),
VariantIOS(name: "se", weight: doNotAllocate, isIncluded: When.always, features: []),
- VariantIOS(name: "ms", weight: 1, isIncluded: When.always, features: [.newOnboardingIntro]),
- VariantIOS(name: "mu", weight: 1, isIncluded: When.always, features: [.newOnboardingIntro, .contextualDaxDialogs]),
- VariantIOS(name: "mx", weight: 1, isIncluded: When.always, features: [.newOnboardingIntroHighlights, .contextualDaxDialogs]),
+ VariantIOS(name: "mh", weight: 1, isIncluded: When.notPadDevice, features: []),
+ VariantIOS(name: "mk", weight: 1, isIncluded: When.notPadDevice, features: [.addToDockIntro]),
+ VariantIOS(name: "mo", weight: 1, isIncluded: When.notPadDevice, features: [.addToDockContextual]),
returningUser
]
diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift
index 15263d9e7b..7ec10ba680 100644
--- a/Core/FeatureFlag.swift
+++ b/Core/FeatureFlag.swift
@@ -32,6 +32,7 @@ public enum FeatureFlag: String {
case autofillFailureReporting
case autofillOnForExistingUsers
case autofillUnknownUsernameCategorization
+ case autofillPartialFormSaves
case incontextSignup
case autoconsentOnByDefault
case history
@@ -56,6 +57,7 @@ public enum FeatureFlag: String {
/// https://app.asana.com/0/1208592102886666/1208613627589762/f
case crashReportOptInStatusResetting
+
case isPrivacyProLaunchedROW
case isPrivacyProLaunchedROWOverride
@@ -100,6 +102,8 @@ extension FeatureFlag: FeatureFlagDescribing {
return .remoteReleasable(.subfeature(AutofillSubfeature.onForExistingUsers))
case .autofillUnknownUsernameCategorization:
return .remoteReleasable(.subfeature(AutofillSubfeature.unknownUsernameCategorization))
+ case .autofillPartialFormSaves:
+ return .remoteReleasable(.subfeature(AutofillSubfeature.partialFormSaves))
case .incontextSignup:
return .remoteReleasable(.feature(.incontextSignup))
case .autoconsentOnByDefault:
diff --git a/DuckDuckGo/PasswordHider.swift b/Core/PasswordHider.swift
similarity index 83%
rename from DuckDuckGo/PasswordHider.swift
rename to Core/PasswordHider.swift
index 1849248f24..ffe3a70929 100644
--- a/DuckDuckGo/PasswordHider.swift
+++ b/Core/PasswordHider.swift
@@ -19,11 +19,15 @@
import Foundation
-struct PasswordHider {
- let password: String
- var hiddenPassword: String {
+public struct PasswordHider {
+ public let password: String
+ public var hiddenPassword: String {
let maximumPasswordDisplayCount = 22
let passwordCount = password.count > maximumPasswordDisplayCount ? maximumPasswordDisplayCount : password.count
return String(repeating: "•", count: passwordCount)
}
+
+ public init(password: String) {
+ self.password = password
+ }
}
diff --git a/Core/Pixel.swift b/Core/Pixel.swift
index 04f48ee03f..ded3c22a2c 100644
--- a/Core/Pixel.swift
+++ b/Core/Pixel.swift
@@ -147,6 +147,7 @@ public struct PixelParameters {
// Autofill
public static let countBucket = "count_bucket"
+ public static let backfilled = "backfilled"
// Privacy Dashboard
public static let daysSinceInstall = "daysSinceInstall"
diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift
index ee2628e3d7..eb924e0142 100644
--- a/Core/PixelEvent.swift
+++ b/Core/PixelEvent.swift
@@ -27,7 +27,8 @@ import NetworkProtection
extension Pixel {
public enum Event {
-
+
+ case appInstall
case appLaunch
case refreshPressed
case pullToRefresh
@@ -59,7 +60,11 @@ extension Pixel {
case tabSwitcherClickCloseTab
case tabSwitcherSwipeCloseTab
case tabSwitchLongPressNewTab
- case tabSwitcherOpenDaily
+ case tabSwitcherOpenedDaily
+
+ case tabSwitcherOpenedFromSerp
+ case tabSwitcherOpenedFromWebsite
+ case tabSwitcherOpenedFromNewTabPage
case settingsDoNotSellShown
case settingsDoNotSellOn
@@ -80,6 +85,7 @@ extension Pixel {
case browsingMenuShare
case browsingMenuCopy
case browsingMenuPrint
+ case browsingMenuListPrint
case browsingMenuFindInPage
case browsingMenuZoom
case browsingMenuDisableProtection
@@ -87,7 +93,8 @@ extension Pixel {
case browsingMenuReportBrokenSite
case browsingMenuFireproof
case browsingMenuAutofill
-
+ case browsingMenuAIChat
+
case addressBarShare
case addressBarSettings
case addressBarCancelPressedOnNTP
@@ -115,7 +122,7 @@ extension Pixel {
case tabBarForwardPressed
case bookmarksButtonPressed
case tabBarBookmarksLongPressed
- case tabBarTabSwitcherPressed
+ case tabBarTabSwitcherOpened
case homeScreenShown
case homeScreenEditFavorite
@@ -309,6 +316,8 @@ extension Pixel {
case autofillOnboardedUser
case autofillToggledOn
case autofillToggledOff
+ case autofillExtensionToggledOn
+ case autofillExtensionToggledOff
case autofillLoginsStacked
case autofillManagementOpened
@@ -329,7 +338,18 @@ extension Pixel {
case getDesktopCopy
case getDesktopShare
-
+
+ case autofillExtensionEnabled
+ case autofillExtensionDisabled
+ case autofillExtensionWelcomeDismiss
+ case autofillExtensionWelcomeLaunchApp
+ case autofillExtensionQuickTypeConfirmed
+ case autofillExtensionQuickTypeCancelled
+ case autofillExtensionPasswordsOpened
+ case autofillExtensionPasswordsDismissed
+ case autofillExtensionPasswordSelected
+ case autofillExtensionPasswordsSearch
+
case autofillJSPixelFired(_ pixel: AutofillUserScript.JSPixel)
case secureVaultError
@@ -503,6 +523,7 @@ extension Pixel {
// MARK: debug pixels
case dbCrashDetected
+ case dbCrashDetectedDaily
case crashOnCrashHandlersSetUp
case crashReportCRCIDMissing
@@ -535,12 +556,12 @@ extension Pixel {
case contentBlockingCompilationFailed(listType: CompileRulesListType,
component: ContentBlockerDebugEvents.Component)
- case contentBlockingCompilationTime
case contentBlockingLookupRulesSucceeded
case contentBlockingFetchLRCSucceeded
case contentBlockingNoMatchInLRC
case contentBlockingLRCMissing
+ case contentBlockingCompilationTaskPerformance(iterationCount: Int, timeBucketAggregation: CompileTimeBucketAggregation)
case ampBlockingRulesCompilationFailed
case webKitDidTerminate
@@ -759,6 +780,8 @@ extension Pixel {
// MARK: Pixel Experiment
case pixelExperimentEnrollment
+
+ // MARK: Settings
case settingsPresented
case settingsSetAsDefault
case settingsVoiceSearchOn
@@ -776,6 +799,26 @@ extension Pixel {
case settingsAccessibilityOpen
case settingsAccessiblityTextZoom
+ case settingsPrivateSearchOpen
+ case settingsEmailProtectionOpen
+ case settingsEmailProtectionEnable
+ case settingsGeneralOpen
+ case settingsSyncOpen
+ case settingsAppearanceOpen
+ case settingsThemeSelectorPressed
+ case settingsAddressBarTopSelected
+ case settingsAddressBarBottomSelected
+ case settingsShowFullURLOn
+ case settingsShowFullURLOff
+ case settingsDataClearingOpen
+ case settingsFireButtonSelectorPressed
+ case settingsDataClearingClearDataOpen
+ case settingsAutomaticallyClearDataOn
+ case settingsAutomaticallyClearDataOff
+ case settingsNextStepsAddAppToDock
+ case settingsNextStepsAddWidget
+ case settingsMoreSearchSettings
+
// Web pixels
case privacyProOfferMonthlyPriceClick
case privacyProOfferYearlyPriceClick
@@ -898,9 +941,12 @@ extension Pixel {
case appDidShowUITime(time: BucketAggregation)
case appDidBecomeActiveTime(time: BucketAggregation)
+ // MARK: AI Chat
+ case aiChatNoRemoteSettingsFound(settings: String)
+ case openAIChatFromAddressBar
+
// MARK: Lifecycle
case appDidTransitionToUnexpectedState
-
}
}
@@ -911,6 +957,7 @@ extension Pixel.Event {
public var name: String {
switch self {
+ case .appInstall: return "m_install"
case .appLaunch: return "ml"
case .refreshPressed: return "m_r"
case .pullToRefresh: return "m_pull-to-reload"
@@ -942,7 +989,11 @@ extension Pixel.Event {
case .tabSwitcherClickCloseTab: return "m_tab_manager_close_tab_click"
case .tabSwitcherSwipeCloseTab: return "m_tab_manager_close_tab_swipe"
case .tabSwitchLongPressNewTab: return "m_tab_manager_long_press_new_tab"
- case .tabSwitcherOpenDaily: return "m_tab_manager_clicked_daily"
+ case .tabSwitcherOpenedDaily: return "m_tab_manager_opened_daily"
+
+ case .tabSwitcherOpenedFromSerp: return "m_tab_manager_open_from_serp"
+ case .tabSwitcherOpenedFromWebsite: return "m_tab_manager_open_from_website"
+ case .tabSwitcherOpenedFromNewTabPage: return "m_tab_manager_open_from_newtabpage"
case .settingsDoNotSellShown: return "ms_dns"
case .settingsDoNotSellOn: return "ms_dns_on"
@@ -951,7 +1002,27 @@ extension Pixel.Event {
case .settingsAutoconsentShown: return "m_settings_autoconsent_shown"
case .settingsAutoconsentOn: return "m_settings_autoconsent_on"
case .settingsAutoconsentOff: return "m_settings_autoconsent_off"
-
+
+ case .settingsPrivateSearchOpen: return "m_settings_private_search_open"
+ case .settingsEmailProtectionOpen: return "m_settings_email_protection_open"
+ case .settingsEmailProtectionEnable: return "m_settings_email_protection_enable"
+ case .settingsGeneralOpen: return "m_settings_general_open"
+ case .settingsSyncOpen: return "m_settings_sync_open"
+ case .settingsAppearanceOpen: return "m_settings_appearance_open"
+ case .settingsThemeSelectorPressed: return "m_settings_theme_selector_pressed"
+ case .settingsAddressBarTopSelected: return "m_settings_address_bar_top_selected"
+ case .settingsAddressBarBottomSelected: return "m_settings_address_bar_bottom_selected"
+ case .settingsShowFullURLOn: return "m_settings_show_full_url_on"
+ case .settingsShowFullURLOff: return "m_settings_show_full_url_off"
+ case .settingsDataClearingOpen: return "m_settings_data_clearing_open"
+ case .settingsFireButtonSelectorPressed: return "m_settings_fire_button_selector_pressed"
+ case .settingsDataClearingClearDataOpen: return "m_settings_data_clearing_clear_data_open"
+ case .settingsAutomaticallyClearDataOn: return "m_settings_automatically_clear_data_on"
+ case .settingsAutomaticallyClearDataOff: return "m_settings_automatically_clear_data_off"
+ case .settingsNextStepsAddAppToDock: return "m_settings_next_steps_add_app_to_dock"
+ case .settingsNextStepsAddWidget: return "m_settings_next_steps_add_widget"
+ case .settingsMoreSearchSettings: return "m_settings_more_search_settings"
+
case .browsingMenuOpened: return "mb"
case .browsingMenuNewTab: return "mb_tb"
case .browsingMenuAddToBookmarks: return "mb_abk"
@@ -962,6 +1033,7 @@ extension Pixel.Event {
case .browsingMenuToggleBrowsingMode: return "mb_dm"
case .browsingMenuCopy: return "mb_cp"
case .browsingMenuPrint: return "mb_pr"
+
case .browsingMenuFindInPage: return "mb_fp"
case .browsingMenuZoom: return "m_menu_page_zoom_taps"
case .browsingMenuDisableProtection: return "mb_wla"
@@ -971,6 +1043,8 @@ extension Pixel.Event {
case .browsingMenuAutofill: return "m_nav_autofill_menu_item_pressed"
case .browsingMenuShare: return "m_browsingmenu_share"
+ case .browsingMenuAIChat: return "m_aichat_menu_tab_icon"
+ case .browsingMenuListPrint: return "m_browsing_menu_list_print"
case .addressBarShare: return "m_addressbar_share"
case .addressBarSettings: return "m_addressbar_settings"
@@ -999,7 +1073,7 @@ extension Pixel.Event {
case .tabBarForwardPressed: return "mt_fw"
case .bookmarksButtonPressed: return "mt_bm"
case .tabBarBookmarksLongPressed: return "mt_bl"
- case .tabBarTabSwitcherPressed: return "mt_tb"
+ case .tabBarTabSwitcherOpened: return "m_tab_manager_opened"
case .bookmarkLaunchList: return "m_bookmark_launch_list"
case .bookmarkLaunchScored: return "m_bookmark_launch_scored"
@@ -1194,6 +1268,8 @@ extension Pixel.Event {
case .autofillOnboardedUser: return "m_autofill_onboardeduser"
case .autofillToggledOn: return "m_autofill_toggled_on"
case .autofillToggledOff: return "m_autofill_toggled_off"
+ case .autofillExtensionToggledOn: return "m_autofill_extension_toggled_on"
+ case .autofillExtensionToggledOff: return "m_autofill_extension_toggled_off"
case .autofillLoginsStacked: return "m_autofill_logins_stacked"
@@ -1223,6 +1299,18 @@ extension Pixel.Event {
case .getDesktopCopy: return "m_get_desktop_copy"
case .getDesktopShare: return "m_get_desktop_share"
+ // Autofill Credential Provider Extension
+ case .autofillExtensionEnabled: return "autofill_extension_enabled"
+ case .autofillExtensionDisabled: return "autofill_extension_disabled"
+ case .autofillExtensionWelcomeDismiss: return "autofill_extension_welcome_dismiss"
+ case .autofillExtensionWelcomeLaunchApp: return "autofill_extension_welcome_launch_app"
+ case .autofillExtensionQuickTypeConfirmed: return "autofill_extension_quicktype_confirmed"
+ case .autofillExtensionQuickTypeCancelled: return "autofill_extension_quicktype_cancelled"
+ case .autofillExtensionPasswordsOpened: return "autofill_extension_passwords_opened"
+ case .autofillExtensionPasswordsDismissed: return "autofill_extension_passwords_dismissed"
+ case .autofillExtensionPasswordSelected: return "autofill_extension_password_selected"
+ case .autofillExtensionPasswordsSearch: return "autofill_extension_passwords_search"
+
case .autofillJSPixelFired(let pixel):
return "m_ios_\(pixel.pixelName)"
@@ -1370,6 +1458,7 @@ extension Pixel.Event {
// MARK: debug pixels
case .dbCrashDetected: return "m_d_crash"
+ case .dbCrashDetectedDaily: return "m_d_crash_daily"
case .crashReportCRCIDMissing: return "crashreporting_crcid-missing"
case .crashReportingSubmissionFailed: return "crashreporting_submission-failed"
case .crashOnCrashHandlersSetUp: return "m_d_crash_on_handlers_setup"
@@ -1402,13 +1491,14 @@ extension Pixel.Event {
case .contentBlockingCompilationFailed(let listType, let component):
return "m_d_content_blocking_\(listType)_\(component)_compilation_failed"
- case .contentBlockingCompilationTime: return "m_content_blocking_compilation_time"
case .contentBlockingLookupRulesSucceeded: return "m_content_blocking_lookup_rules_succeeded"
case .contentBlockingFetchLRCSucceeded: return "m_content_blocking_fetch_lrc_succeeded"
case .contentBlockingNoMatchInLRC: return "m_content_blocking_no_match_in_lrc"
case .contentBlockingLRCMissing: return "m_content_blocking_lrc_missing"
+ case .contentBlockingCompilationTaskPerformance(let iterationCount, let timeBucketAggregation):
+ return "m_content_blocking_compilation_loops_\(iterationCount)_time_\(timeBucketAggregation)"
case .ampBlockingRulesCompilationFailed: return "m_debug_amp_rules_compilation_failed"
case .webKitDidTerminate: return "m_d_wkt"
@@ -1637,6 +1727,8 @@ extension Pixel.Event {
// MARK: Pixel Experiment
case .pixelExperimentEnrollment: return "pixel_experiment_enrollment"
+
+ // MARK: Settings
case .settingsPresented: return "m_settings_presented"
case .settingsSetAsDefault: return "m_settings_set_as_default"
case .settingsVoiceSearchOn: return "m_settings_voice_search_on"
@@ -1792,6 +1884,11 @@ extension Pixel.Event {
case .appDidShowUITime(let time): return "m_debug_app-did-show-ui-time-\(time)"
case .appDidBecomeActiveTime(let time): return "m_debug_app-did-become-active-time-\(time)"
+ // MARK: AI Chat
+ case .aiChatNoRemoteSettingsFound(let settings):
+ return "m_aichat_no_remote_settings_found-\(settings.lowercased())"
+ case .openAIChatFromAddressBar: return "m_aichat_addressbar_icon"
+
// MARK: Lifecycle
case .appDidTransitionToUnexpectedState: return "m_debug_app-did-transition-to-unexpected-state"
@@ -1868,6 +1965,50 @@ extension Pixel.Event {
case blockingAttribution
case attributed
case unknown
-
+
+ }
+
+ public enum CompileTimeBucketAggregation: String, CustomStringConvertible {
+
+ public var description: String { rawValue }
+
+ case lessThan1 = "1"
+ case lessThan2 = "2"
+ case lessThan3 = "3"
+ case lessThan4 = "4"
+ case lessThan5 = "5"
+ case lessThan6 = "6"
+ case lessThan7 = "7"
+ case lessThan8 = "8"
+ case lessThan9 = "9"
+ case lessThan10 = "10"
+ case more
+
+ public init(number: Double) {
+ switch number {
+ case ...1:
+ self = .lessThan1
+ case ...2:
+ self = .lessThan2
+ case ...3:
+ self = .lessThan3
+ case ...4:
+ self = .lessThan4
+ case ...5:
+ self = .lessThan5
+ case ...6:
+ self = .lessThan6
+ case ...7:
+ self = .lessThan7
+ case ...8:
+ self = .lessThan8
+ case ...9:
+ self = .lessThan9
+ case ...10:
+ self = .lessThan10
+ default:
+ self = .more
+ }
+ }
}
}
diff --git a/Core/StatisticsLoader.swift b/Core/StatisticsLoader.swift
index a8001e6077..166e8e5014 100644
--- a/Core/StatisticsLoader.swift
+++ b/Core/StatisticsLoader.swift
@@ -21,6 +21,8 @@ import Common
import Foundation
import BrowserServicesKit
import Networking
+import PixelKit
+import PixelExperimentKit
import os.log
public class StatisticsLoader {
@@ -35,15 +37,24 @@ public class StatisticsLoader {
private let parser = AtbParser()
private let atbPresenceFileMarker = BoolFileMarker(name: .isATBPresent)
private let inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring
+ private let fireSearchExperimentPixels: () -> Void
+ private let fireAppRetentionExperimentPixels: () -> Void
+ private let pixelFiring: PixelFiring.Type
init(statisticsStore: StatisticsStore = StatisticsUserDefaults(),
returnUserMeasurement: ReturnUserMeasurement = KeychainReturnUserMeasurement(),
usageSegmentation: UsageSegmenting = UsageSegmentation(),
- inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring = StorageInconsistencyMonitor()) {
+ inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring = StorageInconsistencyMonitor(),
+ fireAppRetentionExperimentPixels: @escaping () -> Void = PixelKit.fireAppRetentionExperimentPixels,
+ fireSearchExperimentPixels: @escaping () -> Void = PixelKit.fireSearchExperimentPixels,
+ pixelFiring: PixelFiring.Type = Pixel.self) {
self.statisticsStore = statisticsStore
self.returnUserMeasurement = returnUserMeasurement
self.usageSegmentation = usageSegmentation
self.inconsistencyMonitoring = inconsistencyMonitoring
+ self.fireSearchExperimentPixels = fireSearchExperimentPixels
+ self.fireAppRetentionExperimentPixels = fireAppRetentionExperimentPixels
+ self.pixelFiring = pixelFiring
}
public func load(completion: @escaping Completion = {}) {
@@ -94,6 +105,7 @@ public class StatisticsLoader {
completion()
return
}
+ self.fireInstallPixel()
self.statisticsStore.installDate = Date()
self.statisticsStore.atb = atb.version
self.returnUserMeasurement.installCompletedWithATB(atb)
@@ -102,11 +114,26 @@ public class StatisticsLoader {
}
}
+ private func fireInstallPixel() {
+ let formattedLocale = Locale.current.localeIdentifierAsJsonFormat
+ let isReinstall = String(statisticsStore.variant == VariantIOS.returningUser.name)
+ let parameters = [
+ "locale": formattedLocale,
+ "reinstall": isReinstall
+ ]
+ pixelFiring.fire(.appInstall, withAdditionalParameters: parameters, includedParameters: [.appVersion], onComplete: { error in
+ if let error {
+ Logger.general.error("Install pixel failed with error: \(error.localizedDescription, privacy: .public)")
+ }
+ })
+ }
+
private func createATBFileMarker() {
atbPresenceFileMarker?.mark()
}
public func refreshSearchRetentionAtb(completion: @escaping Completion = {}) {
+ fireSearchExperimentPixels()
guard let url = StatisticsDependentURLFactory(statisticsStore: statisticsStore).makeSearchAtbURL() else {
requestInstallStatistics {
self.updateUsageSegmentationAfterInstall(activityType: .search)
@@ -136,6 +163,7 @@ public class StatisticsLoader {
}
public func refreshAppRetentionAtb(completion: @escaping Completion = {}) {
+ fireAppRetentionExperimentPixels()
guard let url = StatisticsDependentURLFactory(statisticsStore: statisticsStore).makeAppAtbURL() else {
requestInstallStatistics {
self.updateUsageSegmentationAfterInstall(activityType: .appUse)
diff --git a/Core/SyncCredentialsAdapter.swift b/Core/SyncCredentialsAdapter.swift
index 7fa4b8c509..888341cc87 100644
--- a/Core/SyncCredentialsAdapter.swift
+++ b/Core/SyncCredentialsAdapter.swift
@@ -33,10 +33,12 @@ public final class SyncCredentialsAdapter {
public static let syncCredentialsPausedStateChanged = SyncBookmarksAdapter.syncBookmarksPausedStateChanged
public static let credentialsSyncLimitReached = Notification.Name("com.duckduckgo.app.SyncCredentialsLimitReached")
let syncErrorHandler: SyncErrorHandling
+ let credentialIdentityStoreManager: AutofillCredentialIdentityStoreManaging
public init(secureVaultFactory: AutofillVaultFactory = AutofillSecureVaultFactory,
secureVaultErrorReporter: SecureVaultReporting,
- syncErrorHandler: SyncErrorHandling) {
+ syncErrorHandler: SyncErrorHandling,
+ tld: TLD) {
syncDidCompletePublisher = syncDidCompleteSubject.eraseToAnyPublisher()
self.secureVaultErrorReporter = secureVaultErrorReporter
self.syncErrorHandler = syncErrorHandler
@@ -45,6 +47,8 @@ public final class SyncCredentialsAdapter {
secureVaultErrorReporter: secureVaultErrorReporter,
errorEvents: CredentialsCleanupErrorHandling()
)
+ credentialIdentityStoreManager = AutofillCredentialIdentityStoreManager(
+ vault: try? secureVaultFactory.makeVault(reporter: secureVaultErrorReporter), tld: tld)
}
public func cleanUpDatabaseAndUpdateSchedule(shouldEnable: Bool) {
@@ -74,9 +78,15 @@ public final class SyncCredentialsAdapter {
syncDidUpdateData: { [weak self] in
self?.syncDidCompleteSubject.send()
self?.syncErrorHandler.syncCredentialsSucceded()
+ },
+ syncDidFinish: { [weak self] credentialsInput in
+ if let credentialsInput, !credentialsInput.modifiedAccounts.isEmpty || !credentialsInput.deletedAccounts.isEmpty {
+ Task {
+ await self?.credentialIdentityStoreManager.updateCredentialStoreWith(updatedAccounts: credentialsInput.modifiedAccounts, deletedAccounts: credentialsInput.deletedAccounts)
+ }
+ }
}
)
-
syncErrorCancellable = provider.syncErrorPublisher
.sink { [weak self] error in
self?.syncErrorHandler.handleCredentialError(error)
diff --git a/Core/SyncDataProviders.swift b/Core/SyncDataProviders.swift
index a32f3b8508..2220b05bd6 100644
--- a/Core/SyncDataProviders.swift
+++ b/Core/SyncDataProviders.swift
@@ -101,7 +101,8 @@ public class SyncDataProviders: DataProvidersSource {
settingHandlers: [SettingSyncHandler],
favoritesDisplayModeStorage: FavoritesDisplayModeStoring,
syncErrorHandler: SyncErrorHandling,
- faviconStoring: FaviconStoring
+ faviconStoring: FaviconStoring,
+ tld: TLD
) {
self.bookmarksDatabase = bookmarksDatabase
self.secureVaultFactory = secureVaultFactory
@@ -112,7 +113,8 @@ public class SyncDataProviders: DataProvidersSource {
faviconStoring: faviconStoring)
credentialsAdapter = SyncCredentialsAdapter(secureVaultFactory: secureVaultFactory,
secureVaultErrorReporter: secureVaultErrorReporter,
- syncErrorHandler: syncErrorHandler)
+ syncErrorHandler: syncErrorHandler,
+ tld: tld)
settingsAdapter = SyncSettingsAdapter(settingHandlers: settingHandlers,
syncErrorHandler: syncErrorHandler)
}
diff --git a/Core/UserAuthenticator.swift b/Core/UserAuthenticator.swift
new file mode 100644
index 0000000000..551a598b4f
--- /dev/null
+++ b/Core/UserAuthenticator.swift
@@ -0,0 +1,101 @@
+//
+// UserAuthenticator.swift
+// DuckDuckGo
+//
+// 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 LocalAuthentication
+import os.log
+
+open class UserAuthenticator {
+
+ public enum AuthError: Error, Equatable {
+ case noAuthAvailable
+ case failedToAuthenticate
+ }
+
+ public enum AuthenticationState {
+ case loggedIn, loggedOut, notAvailable
+ }
+
+ public struct Notifications {
+ public static let invalidateContext = Notification.Name("com.duckduckgo.app.UserAuthenticator.invalidateContext")
+ }
+
+ private var context = LAContext()
+ private var reason: String
+ private var cancelTitle: String
+ @Published public private(set) var state = AuthenticationState.loggedOut
+
+ public init(reason: String, cancelTitle: String) {
+ self.reason = reason
+ self.cancelTitle = cancelTitle
+ }
+
+ public func logOut() {
+ state = .loggedOut
+ }
+
+ public func canAuthenticate() -> Bool {
+ var error: NSError?
+ let canAuthenticate = LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: &error)
+ return canAuthenticate
+ }
+
+ public func canAuthenticateViaBiometrics() -> Bool {
+ var error: NSError?
+ let canAuthenticate = LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
+ return canAuthenticate
+ }
+
+ open func authenticate(completion: ((AuthError?) -> Void)? = nil) {
+
+ if state == .loggedIn {
+ completion?(nil)
+ return
+ }
+
+ context = LAContext()
+ context.localizedCancelTitle = cancelTitle
+ context.interactionNotAllowed = false
+ context.localizedReason = reason
+
+ if canAuthenticate() {
+ let reason = reason
+ context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { [weak self] success, error in
+
+ DispatchQueue.main.async {
+ if success {
+ self?.state = .loggedIn
+ completion?(nil)
+ } else {
+ Logger.general.error("Failed to authenticate: \(error?.localizedDescription ?? "nil", privacy: .public)")
+ completion?(.failedToAuthenticate)
+ }
+ }
+ }
+ } else {
+ state = .notAvailable
+ completion?(.noAuthAvailable)
+ }
+ }
+
+ public func invalidateContext() {
+ context.invalidate()
+ }
+
+}
diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift
index 4858ad964d..4b0b9682ab 100644
--- a/Core/UserDefaultsPropertyWrapper.swift
+++ b/Core/UserDefaultsPropertyWrapper.swift
@@ -95,6 +95,7 @@ public struct UserDefaultsWrapper {
case autofillFillDate = "com.duckduckgo.app.autofill.FillDate"
case autofillOnboardedUser = "com.duckduckgo.app.autofill.OnboardedUser"
case autofillSurveysCompleted = "com.duckduckgo.app.autofill.SurveysCompleted"
+ case autofillExtensionEnabled = "com.duckduckgo.app.autofill.ExtensionEnabled"
case syncPromoBookmarksDismissed = "com.duckduckgo.app.sync.PromoBookmarksDismissed"
case syncPromoPasswordsDismissed = "com.duckduckgo.app.sync.PromoPasswordsDismissed"
diff --git a/Core/ios-config.json b/Core/ios-config.json
index 188f9299e7..4e06aee6ff 100644
--- a/Core/ios-config.json
+++ b/Core/ios-config.json
@@ -1,15 +1,15 @@
{
"readme": "https://github.com/duckduckgo/privacy-configuration",
- "version": 1733157661102,
+ "version": 1734119755860,
"features": {
"adAttributionReporting": {
- "state": "disabled",
+ "state": "enabled",
"exceptions": [],
- "minSupportedVersion": "7.145.0",
+ "minSupportedVersion": "7.146.0",
"settings": {
"includeToken": false
},
- "hash": "1da4782852f0317459b4465d801eda1f"
+ "hash": "4c739e2de28deaaf9408f254041bf113"
},
"adClickAttribution": {
"readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection",
@@ -70,25 +70,31 @@
"hash": "b813ade8472a097ffbd43a3331116fe1"
},
"additionalCampaignPixelParams": {
- "state": "disabled",
+ "state": "enabled",
"exceptions": [],
- "hash": "c292bb627849854515cebbded288ef5a"
+ "settings": {
+ "origins": [
+ "funnel_pro_iosrmf_announcement"
+ ]
+ },
+ "minSupportedVersion": "7.131.0",
+ "hash": "7616abe480cf4aecb67ffbaa71d17217"
},
"aiChat": {
- "state": "enabled",
+ "state": "disabled",
"exceptions": [],
"features": {
"browsingToolbarShortcut": {
- "state": "enabled"
+ "state": "disabled"
},
"addressBarShortcut": {
- "state": "enabled"
+ "state": "disabled"
}
},
"settings": {
"aiChatURL": "https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=4"
},
- "hash": "14908e76cd3a8b4919e03003fd201300"
+ "hash": "4b5209f8cbe489299641ea30ed9a2542"
},
"ampLinks": {
"exceptions": [
@@ -156,6 +162,19 @@
"state": "disabled",
"hash": "728493ef7a1488e4781656d3f9db84aa"
},
+ "apiManipulation": {
+ "state": "disabled",
+ "exceptions": [],
+ "hash": "c292bb627849854515cebbded288ef5a"
+ },
+ "auraExperiment": {
+ "exceptions": [],
+ "settings": {
+ "packages": []
+ },
+ "state": "disabled",
+ "hash": "527fd2e156c7669f001516ee2d3d7c0e"
+ },
"autocompleteTabs": {
"state": "enabled",
"exceptions": [],
@@ -601,6 +620,207 @@
{
"domain": "outlet46.de"
},
+ {
+ "domain": "mytolino.de"
+ },
+ {
+ "domain": "google.ae"
+ },
+ {
+ "domain": "google.at"
+ },
+ {
+ "domain": "google.be"
+ },
+ {
+ "domain": "google.bg"
+ },
+ {
+ "domain": "google.by"
+ },
+ {
+ "domain": "google.ca"
+ },
+ {
+ "domain": "google.ch"
+ },
+ {
+ "domain": "google.cl"
+ },
+ {
+ "domain": "google.co.id"
+ },
+ {
+ "domain": "google.co.il"
+ },
+ {
+ "domain": "google.co.in"
+ },
+ {
+ "domain": "google.co.jp"
+ },
+ {
+ "domain": "google.co.ke"
+ },
+ {
+ "domain": "google.co.kr"
+ },
+ {
+ "domain": "google.co.nz"
+ },
+ {
+ "domain": "google.co.th"
+ },
+ {
+ "domain": "google.co.uk"
+ },
+ {
+ "domain": "google.co.ve"
+ },
+ {
+ "domain": "google.co.za"
+ },
+ {
+ "domain": "google.com"
+ },
+ {
+ "domain": "google.com.ar"
+ },
+ {
+ "domain": "google.com.au"
+ },
+ {
+ "domain": "google.com.br"
+ },
+ {
+ "domain": "google.com.co"
+ },
+ {
+ "domain": "google.com.ec"
+ },
+ {
+ "domain": "google.com.eg"
+ },
+ {
+ "domain": "google.com.hk"
+ },
+ {
+ "domain": "google.com.mx"
+ },
+ {
+ "domain": "google.com.my"
+ },
+ {
+ "domain": "google.com.pe"
+ },
+ {
+ "domain": "google.com.ph"
+ },
+ {
+ "domain": "google.com.pk"
+ },
+ {
+ "domain": "google.com.py"
+ },
+ {
+ "domain": "google.com.sa"
+ },
+ {
+ "domain": "google.com.sg"
+ },
+ {
+ "domain": "google.com.tr"
+ },
+ {
+ "domain": "google.com.tw"
+ },
+ {
+ "domain": "google.com.ua"
+ },
+ {
+ "domain": "google.com.uy"
+ },
+ {
+ "domain": "google.com.vn"
+ },
+ {
+ "domain": "google.cz"
+ },
+ {
+ "domain": "google.de"
+ },
+ {
+ "domain": "google.dk"
+ },
+ {
+ "domain": "google.dz"
+ },
+ {
+ "domain": "google.ee"
+ },
+ {
+ "domain": "google.es"
+ },
+ {
+ "domain": "google.fi"
+ },
+ {
+ "domain": "google.fr"
+ },
+ {
+ "domain": "google.gr"
+ },
+ {
+ "domain": "google.hr"
+ },
+ {
+ "domain": "google.hu"
+ },
+ {
+ "domain": "google.ie"
+ },
+ {
+ "domain": "google.it"
+ },
+ {
+ "domain": "google.lt"
+ },
+ {
+ "domain": "google.lv"
+ },
+ {
+ "domain": "google.nl"
+ },
+ {
+ "domain": "google.no"
+ },
+ {
+ "domain": "google.pl"
+ },
+ {
+ "domain": "google.pt"
+ },
+ {
+ "domain": "google.ro"
+ },
+ {
+ "domain": "google.rs"
+ },
+ {
+ "domain": "google.ru"
+ },
+ {
+ "domain": "google.se"
+ },
+ {
+ "domain": "google.sk"
+ },
+ {
+ "domain": "serif.com"
+ },
+ {
+ "domain": "wetransfer.com"
+ },
{
"domain": "marvel.com"
},
@@ -643,7 +863,7 @@
}
}
},
- "hash": "2c73b04e22812bf92bc2a859e7540c3a"
+ "hash": "7a26ec675103d4e0dd106a46950156d0"
},
"autofillBreakageReporter": {
"state": "enabled",
@@ -724,9 +944,12 @@
}
]
}
+ },
+ "partialFormSaves": {
+ "state": "enabled"
}
},
- "hash": "28d4af98382248e184c4315bd49f4222"
+ "hash": "07b6b3bb0e6ddc4bf3dadbbbd0419478"
},
"backgroundAgentPixelTest": {
"state": "enabled",
@@ -1638,6 +1861,9 @@
{
"domain": "instructure.com"
},
+ {
+ "domain": "duckduckgo.com"
+ },
{
"domain": "marvel.com"
},
@@ -1655,7 +1881,7 @@
}
],
"state": "disabled",
- "hash": "fce0a9ccd7ae060d25e7debe4d8905fb"
+ "hash": "d04093e35c2ea53300a62df3a8a2abea"
},
"customUserAgent": {
"settings": {
@@ -2773,6 +2999,15 @@
}
]
},
+ {
+ "domain": "chase.com",
+ "rules": [
+ {
+ "selector": ".browserupdate",
+ "type": "hide"
+ }
+ ]
+ },
{
"domain": "cleartax.in",
"rules": [
@@ -2791,7 +3026,7 @@
},
{
"selector": "[data-identity='billboard-ad']",
- "type": "hide-empty"
+ "type": "hide"
},
{
"selector": "[data-identity='leaderboard-ad']",
@@ -2994,6 +3229,23 @@
}
]
},
+ {
+ "domain": "e-chords.com",
+ "rules": [
+ {
+ "selector": ".adscifra",
+ "type": "hide"
+ },
+ {
+ "selector": ".banner",
+ "type": "hide-empty"
+ },
+ {
+ "selector": "#banner",
+ "type": "hide-empty"
+ }
+ ]
+ },
{
"domain": "ebay.com",
"rules": [
@@ -3138,6 +3390,15 @@
}
]
},
+ {
+ "domain": "facebook.com",
+ "rules": [
+ {
+ "selector": ".xnw9j1v:has(div > a[href='https://www.facebook.com/help/597429858389632/'])",
+ "type": "hide"
+ }
+ ]
+ },
{
"domain": "fandom.com",
"rules": [
@@ -3461,6 +3722,26 @@
"selector": "div:has(> iframe[src*='prid=19031780'])",
"type": "hide"
},
+ {
+ "selector": "div:has(> iframe[src*='prid=19044580'])",
+ "type": "hide"
+ },
+ {
+ "selector": "div:has(> iframe[src*='prid=19044578'])",
+ "type": "hide"
+ },
+ {
+ "selector": "div:has(> iframe[src*='prid=19044538'])",
+ "type": "hide"
+ },
+ {
+ "selector": "div:has(> iframe[src*='prid=19044687'])",
+ "type": "hide"
+ },
+ {
+ "selector": "div:has(> iframe[src*='prid=19044636'])",
+ "type": "hide"
+ },
{
"selector": "[aria-labelledby='promo-header']",
"type": "hide"
@@ -5210,7 +5491,7 @@
]
},
"state": "enabled",
- "hash": "16e64ea4c926bdf7c865a8b7cbde1b2b"
+ "hash": "e0632ffb8c02bf14874c33ccd66ee99a"
},
"exceptionHandler": {
"exceptions": [
@@ -5613,6 +5894,9 @@
{
"domain": "norton.com"
},
+ {
+ "domain": "madewell.com"
+ },
{
"domain": "marvel.com"
},
@@ -5638,7 +5922,7 @@
"privacy-test-pages.site"
]
},
- "hash": "501bbc6471eb079cb27fa8a2a47467a5"
+ "hash": "cc0c0adfc06919bb973a7636b59b2430"
},
"harmfulApis": {
"settings": {
@@ -5838,11 +6122,6 @@
"exceptions": [],
"hash": "429cea8d27316dc62af04159ec7c42b5"
},
- "loadingBarExp": {
- "exceptions": [],
- "state": "disabled",
- "hash": "728493ef7a1488e4781656d3f9db84aa"
- },
"maliciousSiteProtection": {
"state": "internal",
"exceptions": [],
@@ -6052,7 +6331,7 @@
"minSupportedVersion": "7.136.0"
},
"setAccessTokenCookieForSubscriptionDomains": {
- "state": "disabled",
+ "state": "enabled",
"rollout": {
"steps": [
{
@@ -6065,13 +6344,29 @@
"percent": 100
}
]
- }
+ },
+ "minSupportedVersion": "7.148.0"
},
- "freeTrials": {
- "state": "internal"
+ "privacyProFreeTrialJan25": {
+ "state": "internal",
+ "targets": [
+ {
+ "localeCountry": "US"
+ }
+ ],
+ "cohorts": [
+ {
+ "name": "control",
+ "weight": 1
+ },
+ {
+ "name": "treatment",
+ "weight": 1
+ }
+ ]
}
},
- "hash": "c002a2f7b1299197657574c7328e8f88"
+ "hash": "c30e064d2aa8e0c9dd00c9ed051b98d3"
},
"privacyProtectionsPopup": {
"state": "disabled",
@@ -7150,10 +7445,12 @@
"domains": [
"ah.nl",
"applesfera.com",
+ "asuracomic.net",
"goplay.be",
"itsthevibe.com",
"nytimes.com",
"realmadrid.com",
+ "repretel.com",
"rocketnews24.com",
"solitaired.com",
"stuff.co.nz",
@@ -7171,6 +7468,7 @@
{
"rule": "securepubads.g.doubleclick.net/pagead/ppub_config",
"domains": [
+ "repretel.com",
"rocketnews24.com",
"weather.com",
"wunderground.com"
@@ -8836,6 +9134,7 @@
{
"rule": "a.pub.network/core/prebid-universal-creative.js",
"domains": [
+ "boingboing.net",
"signupgenius.com",
"smithsonianmag.com",
"stockcharts.com",
@@ -9719,7 +10018,7 @@
"domain": "instructure.com"
}
],
- "hash": "f5b235ed3c5c9afdd44edfa3464235cf"
+ "hash": "fe0934622a089920211c50aaf246954c"
},
"trackingCookies1p": {
"settings": {
@@ -10021,6 +10320,26 @@
"exceptions": [],
"state": "disabled",
"hash": "728493ef7a1488e4781656d3f9db84aa"
+ },
+ "experimentTest": {
+ "state": "enabled",
+ "exceptions": [],
+ "features": {
+ "experimentTestAA": {
+ "state": "enabled",
+ "cohorts": [
+ {
+ "name": "control",
+ "weight": 1
+ },
+ {
+ "name": "treatment",
+ "weight": 1
+ }
+ ]
+ }
+ },
+ "hash": "0fe5af5a94e036bf5d78076df6dd771b"
}
},
"unprotectedTemporary": []
diff --git a/Core/trackerData.json b/Core/trackerData.json
index b2123dd778..f09798f0e6 100644
--- a/Core/trackerData.json
+++ b/Core/trackerData.json
@@ -1,6 +1,6 @@
{
"_builtWith": {
- "tracker-radar": "e270978af9ffd0c909a62969df5f894128d41682920fb7a4c913e25e9ab1ecef-4013b4e91930c643394cb31c6c745356f133b04f",
+ "tracker-radar": "1108f03ca047465b2b8d8ab3385ba2cd4f21e1d0111571f8342b3eb0735b1c4a-4013b4e91930c643394cb31c6c745356f133b04f",
"tracker-surrogates": "fe15c53e755af1257774459ecf066f673f485cb0"
},
"readme": "https://github.com/duckduckgo/tracker-blocklists",
@@ -6604,44 +6604,7 @@
"fingerprinting": 2,
"cookies": 0.00112,
"categories": [],
- "default": "ignore",
- "rules": [
- {
- "rule": "coveo\\.com\\/coveo\\.analytics\\.js\\/2\\/coveoua\\.js",
- "fingerprinting": 2,
- "cookies": 0.000163
- },
- {
- "rule": "coveo\\.com\\/coveo\\.analytics\\.js\\/1\\.0\\/coveoua\\.js",
- "fingerprinting": 2,
- "cookies": 0.0000613
- },
- {
- "rule": "coveo\\.com\\/coveo\\.analytics\\.js\\/coveoua\\.js",
- "fingerprinting": 1,
- "cookies": 0.0000681
- },
- {
- "rule": "coveo\\.com\\/coveo\\.analytics\\.js\\/latest\\/coveoua\\.js",
- "fingerprinting": 1,
- "cookies": 0.0000204
- },
- {
- "rule": "coveo\\.com\\/atomic\\/v1\\.110\\/p-f4cb35d7\\.js",
- "fingerprinting": 2,
- "cookies": 0.0000136
- },
- {
- "rule": "coveo\\.com\\/atomic\\/v1\\/p-217d4a01\\.js",
- "fingerprinting": 2,
- "cookies": 0
- },
- {
- "rule": "coveo\\.com\\/coveo\\.analytics\\.js\\/2\\/coveoua\\.browser\\.js",
- "fingerprinting": 2,
- "cookies": 0.0000136
- }
- ]
+ "default": "ignore"
},
"cpmstar.com": {
"domain": "cpmstar.com",
@@ -8652,14 +8615,7 @@
"fingerprinting": 2,
"cookies": 0.00114,
"categories": [],
- "default": "ignore",
- "rules": [
- {
- "rule": "elfsight\\.com\\/apps\\/social-share-buttons\\/release\\/bcbd1ca01fc572c7bc650c98dbe032152aa706f5\\/app\\/socialShareButtons\\.js",
- "fingerprinting": 3,
- "cookies": 0
- }
- ]
+ "default": "ignore"
},
"elitrack.com": {
"domain": "elitrack.com",
@@ -13283,7 +13239,27 @@
"default": "ignore",
"rules": [
{
- "rule": "impervadns\\.net//SP2013v1/Enterprise/JS/",
+ "rule": "impervadns\\.net\\/\\/SP2013v1\\/Enterprise\\/JS\\/foundation\\.min\\.js",
+ "fingerprinting": 1,
+ "cookies": 0.000136
+ },
+ {
+ "rule": "impervadns\\.net\\/\\/SP2013v1\\/Enterprise\\/JS\\/enterprise\\.min\\.js",
+ "fingerprinting": 1,
+ "cookies": 0.000136
+ },
+ {
+ "rule": "impervadns\\.net\\/\\/SP2013v1\\/Enterprise\\/JS\\/jquery\\.cookie\\.js",
+ "fingerprinting": 1,
+ "cookies": 0.000136
+ },
+ {
+ "rule": "impervadns\\.net\\/\\/SP2013v1\\/Enterprise\\/JS\\/knockout\\.min\\.js",
+ "fingerprinting": 1,
+ "cookies": 0.000136
+ },
+ {
+ "rule": "impervadns\\.net\\/\\/SP2013v1\\/Enterprise\\/JS\\/jquery-ui\\.js",
"fingerprinting": 1,
"cookies": 0.000136
},
@@ -15753,30 +15729,9 @@
{
"rule": "listrakbi\\.com/v1/Product"
},
- {
- "rule": "listrakbi\\.com/json/07c34100-1080-4c55-9097-05c7f4a2b19b"
- },
- {
- "rule": "listrakbi\\.com/json/f5f04466-f923-491d-9354-679d54e32def"
- },
- {
- "rule": "listrakbi\\.com/json/eb9d26f3-d7e9-4df8-9800-a79cfaf345be"
- },
{
"rule": "listrakbi\\.com/SadvKLqKgI5T/cart/update"
},
- {
- "rule": "listrakbi\\.com/json/a6c8a6f0-379f-4e0e-8f1a-db4e3efa6123"
- },
- {
- "rule": "listrakbi\\.com/json/86d9476c-c670-4b5d-a33c-bca65c91576f"
- },
- {
- "rule": "listrakbi\\.com/json/043b15b6-cb62-4e61-9d8a-89856bdbf496"
- },
- {
- "rule": "listrakbi\\.com/json/9902c090-0d30-4673-b94b-3851cb8d6035"
- },
{
"rule": "listrakbi\\.com/RmR1Kqh2AYPr/cart/update"
},
@@ -15797,6 +15752,9 @@
},
{
"rule": "listrakbi\\.com/[a-zA-Z0-9]+\\.js"
+ },
+ {
+ "rule": "listrakbi\\.com/json/.*"
}
]
},
@@ -21827,18 +21785,6 @@
{
"rule": "qualtrics\\.com/WRSiteInterceptEngine/"
},
- {
- "rule": "qualtrics\\.com/WRQualtricsShared/Graphics/siteintercept/wr-dialog-close-btn-black\\.png"
- },
- {
- "rule": "qualtrics\\.com/WRQualtricsShared/Graphics/siteintercept/wr-dialog-close-btn-white\\.png"
- },
- {
- "rule": "qualtrics\\.com/WRQualtricsShared/Graphics/siteintercept/building_preview\\.gif"
- },
- {
- "rule": "qualtrics\\.com/WRQualtricsShared/Graphics/siteintercept/remove_screen_capture\\.png"
- },
{
"rule": "qualtrics\\.com/static/prototype-ui-modules/SharedGraphics/siteintercept/svg-close-btn-black-7\\.svg"
},
@@ -21854,12 +21800,6 @@
{
"rule": "qualtrics\\.com/static/prototype-ui-modules/SharedGraphics/siteintercept/svg-close-btn-black-4\\.svg"
},
- {
- "rule": "qualtrics\\.com/WRQualtricsShared/Graphics/siteintercept/popup_shadow_transparent\\.png"
- },
- {
- "rule": "qualtrics\\.com/WRQualtricsShared/Graphics/siteintercept/feedback-blue-right\\.png"
- },
{
"rule": "qualtrics\\.com/ControlPanel/Graphic\\.php"
},
@@ -21874,6 +21814,9 @@
},
{
"rule": "qualtrics\\.com/static/q-siteintercept/"
+ },
+ {
+ "rule": "qualtrics\\.com/WRQualtricsShared/"
}
]
},
@@ -22243,6 +22186,18 @@
"cookies": 0.01,
"default": "block"
},
+ "rebuyengine.com": {
+ "domain": "rebuyengine.com",
+ "owner": {
+ "name": "rebuyengine.com",
+ "displayName": "rebuyengine.com"
+ },
+ "prevalence": 0.00142,
+ "fingerprinting": 2,
+ "cookies": 0.00125,
+ "categories": [],
+ "default": "ignore"
+ },
"reconditerake.com": {
"domain": "reconditerake.com",
"owner": {
@@ -26891,30 +26846,12 @@
"categories": [],
"default": "ignore",
"rules": [
- {
- "rule": "tiktok\\.com/i18n/pixel/events\\.js"
- },
- {
- "rule": "tiktok\\.com/i18n/pixel/static/identify_d1af3\\.js"
- },
{
"rule": "tiktok\\.com/api/v2/pixel"
},
- {
- "rule": "tiktok\\.com/i18n/pixel/sdk\\.js"
- },
{
"rule": "tiktok\\.com/api/v2/performance_interaction"
},
- {
- "rule": "tiktok\\.com/i18n/pixel/config\\.js"
- },
- {
- "rule": "tiktok\\.com/i18n/pixel/disable_cookie"
- },
- {
- "rule": "tiktok\\.com/i18n/pixel/static/identify_821f6\\.js"
- },
{
"rule": "tiktok\\.com/v1/user/webid"
},
@@ -26934,13 +26871,7 @@
"rule": "tiktok\\.com/oembed"
},
{
- "rule": "tiktok\\.com/i18n/pixel/enable_cookie"
- },
- {
- "rule": "tiktok\\.com/i18n/pixel/static/identify_a7248\\.js"
- },
- {
- "rule": "tiktok\\.com/i18n/pixel/static/main\\..*\\.js"
+ "rule": "tiktok\\.com/i18n/pixel/"
}
]
},
@@ -32665,17 +32596,6 @@
"cookies": 0.01,
"default": "block"
},
- "acceptableauthority.com": {
- "domain": "acceptableauthority.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (acceptableauthority.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"accountsdoor.com": {
"domain": "accountsdoor.com",
"owner": {
@@ -32951,17 +32871,6 @@
"cookies": 0.01,
"default": "block"
},
- "alertarithmetic.com": {
- "domain": "alertarithmetic.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (alertarithmetic.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"aliasanvil.com": {
"domain": "aliasanvil.com",
"owner": {
@@ -35107,17 +35016,6 @@
"cookies": 0.01,
"default": "block"
},
- "concernedchange.com": {
- "domain": "concernedchange.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (concernedchange.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"concernedchickens.com": {
"domain": "concernedchickens.com",
"owner": {
@@ -37516,17 +37414,6 @@
"cookies": 0.01,
"default": "block"
},
- "fertilefeeling.com": {
- "domain": "fertilefeeling.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (fertilefeeling.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"fewjuice.com": {
"domain": "fewjuice.com",
"owner": {
@@ -38913,17 +38800,6 @@
"cookies": 0.01,
"default": "block"
},
- "hatefulrequest.com": {
- "domain": "hatefulrequest.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (hatefulrequest.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"headydegree.com": {
"domain": "headydegree.com",
"owner": {
@@ -41454,17 +41330,6 @@
"cookies": 0.01,
"default": "block"
},
- "numberlessring.com": {
- "domain": "numberlessring.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (numberlessring.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"numerousnest.com": {
"domain": "numerousnest.com",
"owner": {
@@ -42928,6 +42793,17 @@
"cookies": 0.01,
"default": "block"
},
+ "razzweb.com": {
+ "domain": "razzweb.com",
+ "owner": {
+ "name": "Leven Labs, Inc. DBA Admiral (razzweb.com)",
+ "displayName": "Admiral"
+ },
+ "prevalence": 0.0107,
+ "fingerprinting": 1,
+ "cookies": 0.01,
+ "default": "block"
+ },
"reactjspdf.com": {
"domain": "reactjspdf.com",
"owner": {
@@ -43082,17 +42958,6 @@
"cookies": 0.01,
"default": "block"
},
- "reflectivestatement.com": {
- "domain": "reflectivestatement.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (reflectivestatement.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"refundradar.com": {
"domain": "refundradar.com",
"owner": {
@@ -43500,6 +43365,17 @@
"cookies": 0.01,
"default": "block"
},
+ "sableshelf.com": {
+ "domain": "sableshelf.com",
+ "owner": {
+ "name": "Leven Labs, Inc. DBA Admiral (sableshelf.com)",
+ "displayName": "Admiral"
+ },
+ "prevalence": 0.0107,
+ "fingerprinting": 1,
+ "cookies": 0.01,
+ "default": "block"
+ },
"sablesmile.com": {
"domain": "sablesmile.com",
"owner": {
@@ -43588,6 +43464,17 @@
"cookies": 0.01,
"default": "block"
},
+ "samuraibots.com": {
+ "domain": "samuraibots.com",
+ "owner": {
+ "name": "Leven Labs, Inc. DBA Admiral (samuraibots.com)",
+ "displayName": "Admiral"
+ },
+ "prevalence": 0.0107,
+ "fingerprinting": 1,
+ "cookies": 0.01,
+ "default": "block"
+ },
"sandstrophies.com": {
"domain": "sandstrophies.com",
"owner": {
@@ -43841,17 +43728,6 @@
"cookies": 0.01,
"default": "block"
},
- "scribbleson.com": {
- "domain": "scribbleson.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (scribbleson.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"scrollservice.com": {
"domain": "scrollservice.com",
"owner": {
@@ -43940,17 +43816,6 @@
"cookies": 0.01,
"default": "block"
},
- "seemlysuggestion.com": {
- "domain": "seemlysuggestion.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (seemlysuggestion.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"selfishsea.com": {
"domain": "selfishsea.com",
"owner": {
@@ -44171,6 +44036,17 @@
"cookies": 0.01,
"default": "block"
},
+ "shallowart.com": {
+ "domain": "shallowart.com",
+ "owner": {
+ "name": "Leven Labs, Inc. DBA Admiral (shallowart.com)",
+ "displayName": "Admiral"
+ },
+ "prevalence": 0.0107,
+ "fingerprinting": 1,
+ "cookies": 0.01,
+ "default": "block"
+ },
"shallowblade.com": {
"domain": "shallowblade.com",
"owner": {
@@ -44402,17 +44278,6 @@
"cookies": 0.01,
"default": "block"
},
- "sinkbooks.com": {
- "domain": "sinkbooks.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (sinkbooks.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"sixscissors.com": {
"domain": "sixscissors.com",
"owner": {
@@ -45293,17 +45158,6 @@
"cookies": 0.01,
"default": "block"
},
- "steepscale.com": {
- "domain": "steepscale.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (steepscale.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"steepsister.com": {
"domain": "steepsister.com",
"owner": {
@@ -45491,17 +45345,6 @@
"cookies": 0.01,
"default": "block"
},
- "strangersponge.com": {
- "domain": "strangersponge.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (strangersponge.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"strangesink.com": {
"domain": "strangesink.com",
"owner": {
@@ -45678,17 +45521,6 @@
"cookies": 0.01,
"default": "block"
},
- "sugarfriction.com": {
- "domain": "sugarfriction.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (sugarfriction.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"suggestionbridge.com": {
"domain": "suggestionbridge.com",
"owner": {
@@ -45777,17 +45609,6 @@
"cookies": 0.01,
"default": "block"
},
- "swelteringsleep.com": {
- "domain": "swelteringsleep.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (swelteringsleep.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"swimfreely.com": {
"domain": "swimfreely.com",
"owner": {
@@ -45942,17 +45763,6 @@
"cookies": 0.01,
"default": "block"
},
- "tastelesstrucks.com": {
- "domain": "tastelesstrucks.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (tastelesstrucks.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"tastesnake.com": {
"domain": "tastesnake.com",
"owner": {
@@ -47251,17 +47061,6 @@
"cookies": 0.01,
"default": "block"
},
- "voicelessvein.com": {
- "domain": "voicelessvein.com",
- "owner": {
- "name": "Leven Labs, Inc. DBA Admiral (voicelessvein.com)",
- "displayName": "Admiral"
- },
- "prevalence": 0.0107,
- "fingerprinting": 1,
- "cookies": 0.01,
- "default": "block"
- },
"voidgoo.com": {
"domain": "voidgoo.com",
"owner": {
@@ -47548,6 +47347,17 @@
"cookies": 0.01,
"default": "block"
},
+ "wildwoodavenue.com": {
+ "domain": "wildwoodavenue.com",
+ "owner": {
+ "name": "Leven Labs, Inc. DBA Admiral (wildwoodavenue.com)",
+ "displayName": "Admiral"
+ },
+ "prevalence": 0.0107,
+ "fingerprinting": 1,
+ "cookies": 0.01,
+ "default": "block"
+ },
"wirecomic.com": {
"domain": "wirecomic.com",
"owner": {
@@ -55488,6 +55298,13 @@
"prevalence": 0.0136,
"displayName": "Rating-Widget"
},
+ "rebuyengine.com": {
+ "domains": [
+ "rebuyengine.com"
+ ],
+ "prevalence": 0,
+ "displayName": "rebuyengine.com"
+ },
"Recruitics LLC": {
"domains": [
"recruitics.com"
@@ -57949,13 +57766,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (acceptableauthority.com)": {
- "domains": [
- "acceptableauthority.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (accountsdoor.com)": {
"domains": [
"accountsdoor.com"
@@ -58138,13 +57948,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (alertarithmetic.com)": {
- "domains": [
- "alertarithmetic.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (aliasanvil.com)": {
"domains": [
"aliasanvil.com"
@@ -59755,13 +59558,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (concernedchange.com)": {
- "domains": [
- "concernedchange.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (concernedchickens.com)": {
"domains": [
"concernedchickens.com"
@@ -61477,13 +61273,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (fertilefeeling.com)": {
- "domains": [
- "fertilefeeling.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (fewjuice.com)": {
"domains": [
"fewjuice.com"
@@ -62520,13 +62309,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (hatefulrequest.com)": {
- "domains": [
- "hatefulrequest.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (headydegree.com)": {
"domains": [
"headydegree.com"
@@ -64284,13 +64066,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (numberlessring.com)": {
- "domains": [
- "numberlessring.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (numerousnest.com)": {
"domains": [
"numerousnest.com"
@@ -65418,6 +65193,13 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
+ "Leven Labs, Inc. DBA Admiral (razzweb.com)": {
+ "domains": [
+ "razzweb.com"
+ ],
+ "prevalence": 0.0107,
+ "displayName": "Admiral"
+ },
"Leven Labs, Inc. DBA Admiral (reactjspdf.com)": {
"domains": [
"reactjspdf.com"
@@ -65537,13 +65319,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (reflectivestatement.com)": {
- "domains": [
- "reflectivestatement.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (refundradar.com)": {
"domains": [
"refundradar.com"
@@ -65866,6 +65641,13 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
+ "Leven Labs, Inc. DBA Admiral (sableshelf.com)": {
+ "domains": [
+ "sableshelf.com"
+ ],
+ "prevalence": 0.0107,
+ "displayName": "Admiral"
+ },
"Leven Labs, Inc. DBA Admiral (sablesmile.com)": {
"domains": [
"sablesmile.com"
@@ -65936,6 +65718,13 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
+ "Leven Labs, Inc. DBA Admiral (samuraibots.com)": {
+ "domains": [
+ "samuraibots.com"
+ ],
+ "prevalence": 0.0107,
+ "displayName": "Admiral"
+ },
"Leven Labs, Inc. DBA Admiral (sandstrophies.com)": {
"domains": [
"sandstrophies.com"
@@ -66153,13 +65942,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (scribbleson.com)": {
- "domains": [
- "scribbleson.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (scribblestring.com)": {
"domains": [
"scribblestring.com"
@@ -66230,13 +66012,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (seemlysuggestion.com)": {
- "domains": [
- "seemlysuggestion.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (selectivesummer.com)": {
"domains": [
"selectivesummer.com"
@@ -66398,6 +66173,13 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
+ "Leven Labs, Inc. DBA Admiral (shallowart.com)": {
+ "domains": [
+ "shallowart.com"
+ ],
+ "prevalence": 0.0107,
+ "displayName": "Admiral"
+ },
"Leven Labs, Inc. DBA Admiral (shallowblade.com)": {
"domains": [
"shallowblade.com"
@@ -66580,13 +66362,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (sinkbooks.com)": {
- "domains": [
- "sinkbooks.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (sixauthority.com)": {
"domains": [
"sixauthority.com"
@@ -67266,13 +67041,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (steepscale.com)": {
- "domains": [
- "steepscale.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (steepsister.com)": {
"domains": [
"steepsister.com"
@@ -67427,13 +67195,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (strangersponge.com)": {
- "domains": [
- "strangersponge.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (strangesink.com)": {
"domains": [
"strangesink.com"
@@ -67581,13 +67342,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (sugarfriction.com)": {
- "domains": [
- "sugarfriction.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (suggestionbridge.com)": {
"domains": [
"suggestionbridge.com"
@@ -67672,13 +67426,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (swelteringsleep.com)": {
- "domains": [
- "swelteringsleep.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (swimfreely.com)": {
"domains": [
"swimfreely.com"
@@ -67784,13 +67531,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (tastelesstrucks.com)": {
- "domains": [
- "tastelesstrucks.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (tastesnake.com)": {
"domains": [
"tastesnake.com"
@@ -68708,13 +68448,6 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
- "Leven Labs, Inc. DBA Admiral (voicelessvein.com)": {
- "domains": [
- "voicelessvein.com"
- ],
- "prevalence": 0.0107,
- "displayName": "Admiral"
- },
"Leven Labs, Inc. DBA Admiral (voidgoo.com)": {
"domains": [
"voidgoo.com"
@@ -68925,6 +68658,13 @@
"prevalence": 0.0107,
"displayName": "Admiral"
},
+ "Leven Labs, Inc. DBA Admiral (wildwoodavenue.com)": {
+ "domains": [
+ "wildwoodavenue.com"
+ ],
+ "prevalence": 0.0107,
+ "displayName": "Admiral"
+ },
"Leven Labs, Inc. DBA Admiral (wirecomic.com)": {
"domains": [
"wirecomic.com"
@@ -69842,7 +69582,6 @@
"abstractedauthority.com": "Leven Labs, Inc. DBA Admiral (abstractedauthority.com)",
"absurdapple.com": "Leven Labs, Inc. DBA Admiral (absurdapple.com)",
"abundantcoin.com": "Leven Labs, Inc. DBA Admiral (abundantcoin.com)",
- "acceptableauthority.com": "Leven Labs, Inc. DBA Admiral (acceptableauthority.com)",
"accountsdoor.com": "Leven Labs, Inc. DBA Admiral (accountsdoor.com)",
"accurateanimal.com": "Leven Labs, Inc. DBA Admiral (accurateanimal.com)",
"accuratecoal.com": "Leven Labs, Inc. DBA Admiral (accuratecoal.com)",
@@ -69869,7 +69608,6 @@
"aheadgrow.com": "Leven Labs, Inc. DBA Admiral (aheadgrow.com)",
"aheadmachine.com": "Leven Labs, Inc. DBA Admiral (aheadmachine.com)",
"ak0gsh40.com": "Leven Labs, Inc. DBA Admiral (ak0gsh40.com)",
- "alertarithmetic.com": "Leven Labs, Inc. DBA Admiral (alertarithmetic.com)",
"aliasanvil.com": "Leven Labs, Inc. DBA Admiral (aliasanvil.com)",
"alikeaddition.com": "Leven Labs, Inc. DBA Admiral (alikeaddition.com)",
"aliveachiever.com": "Leven Labs, Inc. DBA Admiral (aliveachiever.com)",
@@ -70100,7 +69838,6 @@
"comfygoodness.com": "Leven Labs, Inc. DBA Admiral (comfygoodness.com)",
"comparereaction.com": "Leven Labs, Inc. DBA Admiral (comparereaction.com)",
"compiledoctor.com": "Leven Labs, Inc. DBA Admiral (compiledoctor.com)",
- "concernedchange.com": "Leven Labs, Inc. DBA Admiral (concernedchange.com)",
"concernedchickens.com": "Leven Labs, Inc. DBA Admiral (concernedchickens.com)",
"condemnedcomb.com": "Leven Labs, Inc. DBA Admiral (condemnedcomb.com)",
"conditionchange.com": "Leven Labs, Inc. DBA Admiral (conditionchange.com)",
@@ -70346,7 +70083,6 @@
"feeblestamp.com": "Leven Labs, Inc. DBA Admiral (feeblestamp.com)",
"feignedfaucet.com": "Leven Labs, Inc. DBA Admiral (feignedfaucet.com)",
"fernwaycloud.com": "Leven Labs, Inc. DBA Admiral (fernwaycloud.com)",
- "fertilefeeling.com": "Leven Labs, Inc. DBA Admiral (fertilefeeling.com)",
"fewjuice.com": "Leven Labs, Inc. DBA Admiral (fewjuice.com)",
"fewkittens.com": "Leven Labs, Inc. DBA Admiral (fewkittens.com)",
"finalizeforce.com": "Leven Labs, Inc. DBA Admiral (finalizeforce.com)",
@@ -70495,7 +70231,6 @@
"harborcub.com": "Leven Labs, Inc. DBA Admiral (harborcub.com)",
"harmonicbamboo.com": "Leven Labs, Inc. DBA Admiral (harmonicbamboo.com)",
"harmonywing.com": "Leven Labs, Inc. DBA Admiral (harmonywing.com)",
- "hatefulrequest.com": "Leven Labs, Inc. DBA Admiral (hatefulrequest.com)",
"headydegree.com": "Leven Labs, Inc. DBA Admiral (headydegree.com)",
"headyhook.com": "Leven Labs, Inc. DBA Admiral (headyhook.com)",
"healflowers.com": "Leven Labs, Inc. DBA Admiral (healflowers.com)",
@@ -70747,7 +70482,6 @@
"notifyglass.com": "Leven Labs, Inc. DBA Admiral (notifyglass.com)",
"nudgeduck.com": "Leven Labs, Inc. DBA Admiral (nudgeduck.com)",
"nullnorth.com": "Leven Labs, Inc. DBA Admiral (nullnorth.com)",
- "numberlessring.com": "Leven Labs, Inc. DBA Admiral (numberlessring.com)",
"numerousnest.com": "Leven Labs, Inc. DBA Admiral (numerousnest.com)",
"nutritiousbean.com": "Leven Labs, Inc. DBA Admiral (nutritiousbean.com)",
"nuttyorganization.com": "Leven Labs, Inc. DBA Admiral (nuttyorganization.com)",
@@ -70909,6 +70643,7 @@
"rangergustav.com": "Leven Labs, Inc. DBA Admiral (rangergustav.com)",
"rarestcandy.com": "Leven Labs, Inc. DBA Admiral (rarestcandy.com)",
"raresummer.com": "Leven Labs, Inc. DBA Admiral (raresummer.com)",
+ "razzweb.com": "Leven Labs, Inc. DBA Admiral (razzweb.com)",
"reactjspdf.com": "Leven Labs, Inc. DBA Admiral (reactjspdf.com)",
"readingguilt.com": "Leven Labs, Inc. DBA Admiral (readingguilt.com)",
"readymoon.com": "Leven Labs, Inc. DBA Admiral (readymoon.com)",
@@ -70926,7 +70661,6 @@
"reconditeprison.com": "Leven Labs, Inc. DBA Admiral (reconditeprison.com)",
"reconditerake.com": "Leven Labs, Inc. DBA Admiral (reconditerake.com)",
"reconditerespect.com": "Leven Labs, Inc. DBA Admiral (reconditerespect.com)",
- "reflectivestatement.com": "Leven Labs, Inc. DBA Admiral (reflectivestatement.com)",
"refundradar.com": "Leven Labs, Inc. DBA Admiral (refundradar.com)",
"regexmail.com": "Leven Labs, Inc. DBA Admiral (regexmail.com)",
"regularplants.com": "Leven Labs, Inc. DBA Admiral (regularplants.com)",
@@ -70973,6 +70707,7 @@
"ruthlessdegree.com": "Leven Labs, Inc. DBA Admiral (ruthlessdegree.com)",
"ruthlessmilk.com": "Leven Labs, Inc. DBA Admiral (ruthlessmilk.com)",
"sableloss.com": "Leven Labs, Inc. DBA Admiral (sableloss.com)",
+ "sableshelf.com": "Leven Labs, Inc. DBA Admiral (sableshelf.com)",
"sablesmile.com": "Leven Labs, Inc. DBA Admiral (sablesmile.com)",
"sablesong.com": "Leven Labs, Inc. DBA Admiral (sablesong.com)",
"sadloaf.com": "Leven Labs, Inc. DBA Admiral (sadloaf.com)",
@@ -70983,6 +70718,7 @@
"samesticks.com": "Leven Labs, Inc. DBA Admiral (samesticks.com)",
"samestretch.com": "Leven Labs, Inc. DBA Admiral (samestretch.com)",
"samplesamba.com": "Leven Labs, Inc. DBA Admiral (samplesamba.com)",
+ "samuraibots.com": "Leven Labs, Inc. DBA Admiral (samuraibots.com)",
"sandstrophies.com": "Leven Labs, Inc. DBA Admiral (sandstrophies.com)",
"satisfycork.com": "Leven Labs, Inc. DBA Admiral (satisfycork.com)",
"savoryorange.com": "Leven Labs, Inc. DBA Admiral (savoryorange.com)",
@@ -71014,7 +70750,6 @@
"screechingfurniture.com": "Leven Labs, Inc. DBA Admiral (screechingfurniture.com)",
"screechingstocking.com": "Leven Labs, Inc. DBA Admiral (screechingstocking.com)",
"screechingstove.com": "Leven Labs, Inc. DBA Admiral (screechingstove.com)",
- "scribbleson.com": "Leven Labs, Inc. DBA Admiral (scribbleson.com)",
"scribblestring.com": "Leven Labs, Inc. DBA Admiral (scribblestring.com)",
"scrollservice.com": "Leven Labs, Inc. DBA Admiral (scrollservice.com)",
"scrubswim.com": "Leven Labs, Inc. DBA Admiral (scrubswim.com)",
@@ -71025,7 +70760,6 @@
"secretspiders.com": "Leven Labs, Inc. DBA Admiral (secretspiders.com)",
"secretturtle.com": "Leven Labs, Inc. DBA Admiral (secretturtle.com)",
"seedscissors.com": "Leven Labs, Inc. DBA Admiral (seedscissors.com)",
- "seemlysuggestion.com": "Leven Labs, Inc. DBA Admiral (seemlysuggestion.com)",
"selectivesummer.com": "Leven Labs, Inc. DBA Admiral (selectivesummer.com)",
"selfishsea.com": "Leven Labs, Inc. DBA Admiral (selfishsea.com)",
"selfishsnake.com": "Leven Labs, Inc. DBA Admiral (selfishsnake.com)",
@@ -71049,6 +70783,7 @@
"shakyseat.com": "Leven Labs, Inc. DBA Admiral (shakyseat.com)",
"shakysurprise.com": "Leven Labs, Inc. DBA Admiral (shakysurprise.com)",
"shakytaste.com": "Leven Labs, Inc. DBA Admiral (shakytaste.com)",
+ "shallowart.com": "Leven Labs, Inc. DBA Admiral (shallowart.com)",
"shallowblade.com": "Leven Labs, Inc. DBA Admiral (shallowblade.com)",
"shamerain.com": "Leven Labs, Inc. DBA Admiral (shamerain.com)",
"shapecomb.com": "Leven Labs, Inc. DBA Admiral (shapecomb.com)",
@@ -71075,7 +70810,6 @@
"sincerepelican.com": "Leven Labs, Inc. DBA Admiral (sincerepelican.com)",
"sinceresubstance.com": "Leven Labs, Inc. DBA Admiral (sinceresubstance.com)",
"singroot.com": "Leven Labs, Inc. DBA Admiral (singroot.com)",
- "sinkbooks.com": "Leven Labs, Inc. DBA Admiral (sinkbooks.com)",
"sixauthority.com": "Leven Labs, Inc. DBA Admiral (sixauthority.com)",
"sixscissors.com": "Leven Labs, Inc. DBA Admiral (sixscissors.com)",
"sizzlingsmoke.com": "Leven Labs, Inc. DBA Admiral (sizzlingsmoke.com)",
@@ -71173,7 +70907,6 @@
"steadfastsystem.com": "Leven Labs, Inc. DBA Admiral (steadfastsystem.com)",
"steadycopper.com": "Leven Labs, Inc. DBA Admiral (steadycopper.com)",
"stealsteel.com": "Leven Labs, Inc. DBA Admiral (stealsteel.com)",
- "steepscale.com": "Leven Labs, Inc. DBA Admiral (steepscale.com)",
"steepsister.com": "Leven Labs, Inc. DBA Admiral (steepsister.com)",
"steepsquirrel.com": "Leven Labs, Inc. DBA Admiral (steepsquirrel.com)",
"stepcattle.com": "Leven Labs, Inc. DBA Admiral (stepcattle.com)",
@@ -71196,7 +70929,6 @@
"stormyachiever.com": "Leven Labs, Inc. DBA Admiral (stormyachiever.com)",
"straightnest.com": "Leven Labs, Inc. DBA Admiral (straightnest.com)",
"strangeclocks.com": "Leven Labs, Inc. DBA Admiral (strangeclocks.com)",
- "strangersponge.com": "Leven Labs, Inc. DBA Admiral (strangersponge.com)",
"strangesink.com": "Leven Labs, Inc. DBA Admiral (strangesink.com)",
"streetsort.com": "Leven Labs, Inc. DBA Admiral (streetsort.com)",
"stretchsister.com": "Leven Labs, Inc. DBA Admiral (stretchsister.com)",
@@ -71218,7 +70950,6 @@
"succeedscene.com": "Leven Labs, Inc. DBA Admiral (succeedscene.com)",
"successfulscent.com": "Leven Labs, Inc. DBA Admiral (successfulscent.com)",
"suddensoda.com": "Leven Labs, Inc. DBA Admiral (suddensoda.com)",
- "sugarfriction.com": "Leven Labs, Inc. DBA Admiral (sugarfriction.com)",
"suggestionbridge.com": "Leven Labs, Inc. DBA Admiral (suggestionbridge.com)",
"sulkycook.com": "Leven Labs, Inc. DBA Admiral (sulkycook.com)",
"summerobject.com": "Leven Labs, Inc. DBA Admiral (summerobject.com)",
@@ -71231,7 +70962,6 @@
"suspectmark.com": "Leven Labs, Inc. DBA Admiral (suspectmark.com)",
"swankysquare.com": "Leven Labs, Inc. DBA Admiral (swankysquare.com)",
"swellstocking.com": "Leven Labs, Inc. DBA Admiral (swellstocking.com)",
- "swelteringsleep.com": "Leven Labs, Inc. DBA Admiral (swelteringsleep.com)",
"swimfreely.com": "Leven Labs, Inc. DBA Admiral (swimfreely.com)",
"swingslip.com": "Leven Labs, Inc. DBA Admiral (swingslip.com)",
"swordgoose.com": "Leven Labs, Inc. DBA Admiral (swordgoose.com)",
@@ -71247,7 +70977,6 @@
"tangyamount.com": "Leven Labs, Inc. DBA Admiral (tangyamount.com)",
"tangycover.com": "Leven Labs, Inc. DBA Admiral (tangycover.com)",
"tastelesstrees.com": "Leven Labs, Inc. DBA Admiral (tastelesstrees.com)",
- "tastelesstrucks.com": "Leven Labs, Inc. DBA Admiral (tastelesstrucks.com)",
"tastesnake.com": "Leven Labs, Inc. DBA Admiral (tastesnake.com)",
"tawdryson.com": "Leven Labs, Inc. DBA Admiral (tawdryson.com)",
"tdzvm.pw": "Leven Labs, Inc. DBA Admiral (tdzvm.pw)",
@@ -71379,7 +71108,6 @@
"vividfrost.com": "Leven Labs, Inc. DBA Admiral (vividfrost.com)",
"vividmeadow.com": "Leven Labs, Inc. DBA Admiral (vividmeadow.com)",
"vividplume.com": "Leven Labs, Inc. DBA Admiral (vividplume.com)",
- "voicelessvein.com": "Leven Labs, Inc. DBA Admiral (voicelessvein.com)",
"voidgoo.com": "Leven Labs, Inc. DBA Admiral (voidgoo.com)",
"volatileprofit.com": "Leven Labs, Inc. DBA Admiral (volatileprofit.com)",
"volatilevessel.com": "Leven Labs, Inc. DBA Admiral (volatilevessel.com)",
@@ -71410,6 +71138,7 @@
"whisperingsummit.com": "Leven Labs, Inc. DBA Admiral (whisperingsummit.com)",
"whispermeeting.com": "Leven Labs, Inc. DBA Admiral (whispermeeting.com)",
"wildcommittee.com": "Leven Labs, Inc. DBA Admiral (wildcommittee.com)",
+ "wildwoodavenue.com": "Leven Labs, Inc. DBA Admiral (wildwoodavenue.com)",
"wirecomic.com": "Leven Labs, Inc. DBA Admiral (wirecomic.com)",
"wiredforcoffee.com": "Leven Labs, Inc. DBA Admiral (wiredforcoffee.com)",
"wirypaste.com": "Leven Labs, Inc. DBA Admiral (wirypaste.com)",
@@ -74914,6 +74643,7 @@
"secretmag.ru": "Rambler Internet Holding, LLC",
"top100.ru": "Rambler Internet Holding, LLC",
"rating-widget.com": "Rating-Widget, Inc.",
+ "rebuyengine.com": "rebuyengine.com",
"recruitics.com": "Recruitics LLC",
"dubsmash.com": "Reddit Inc.",
"redd.it": "Reddit Inc.",
diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj
index a3974aa725..991ba96441 100644
--- a/DuckDuckGo.xcodeproj/project.pbxproj
+++ b/DuckDuckGo.xcodeproj/project.pbxproj
@@ -137,6 +137,8 @@
310D09212799FD1A00DC0060 /* MIMEType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D09202799FD1A00DC0060 /* MIMEType.swift */; };
310E79BD2949CAA5007C49E8 /* FireButtonReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310E79BC2949CAA5007C49E8 /* FireButtonReferenceTests.swift */; };
310ECFDD282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310ECFDC282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift */; };
+ 310EEA2F2CFFCDC60043CA1A /* AIChatSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310EEA2E2CFFCDBF0043CA1A /* AIChatSettingsTests.swift */; };
+ 311711912D00E5500063AC3D /* OmnibarAccessoryHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311711902D00E53A0063AC3D /* OmnibarAccessoryHandling.swift */; };
311BD1AD2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1AC2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift */; };
311BD1AF2836BB4200AEF6C1 /* AutofillItemsLockedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1AE2836BB4200AEF6C1 /* AutofillItemsLockedView.swift */; };
311BD1B12836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1B02836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift */; };
@@ -157,17 +159,21 @@
3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3157B43727F4C8490042D3D7 /* FaviconsHelper.swift */; };
31584616281AFB46004ADB8B /* AutofillLoginDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31584615281AFB46004ADB8B /* AutofillLoginDetailsViewController.swift */; };
3158461A281B08F5004ADB8B /* AutofillLoginListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31584619281B08F5004ADB8B /* AutofillLoginListViewModel.swift */; };
+ 315C77822CFA41A400699683 /* AIChat in Frameworks */ = {isa = PBXBuildFile; productRef = 315C77812CFA41A400699683 /* AIChat */; };
3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3161D13127AC161B00285CF6 /* DownloadMetadata.swift */; };
31669B9A28020A460071CC18 /* SaveLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31669B9928020A460071CC18 /* SaveLoginViewModel.swift */; };
316790E52C9352190090B0A2 /* MarketplaceAdPostbackManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316790E42C9352190090B0A2 /* MarketplaceAdPostbackManagerTests.swift */; };
316931D727BD10BB0095F5ED /* SaveToDownloadsAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316931D627BD10BB0095F5ED /* SaveToDownloadsAlert.swift */; };
316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316931D827BD22A80095F5ED /* DownloadActionMessageViewHelper.swift */; };
+ 316AA45A2CF8E31F00A2ED28 /* AIChatSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316AA4592CF8E31F00A2ED28 /* AIChatSettings.swift */; };
3170048227A9504F00C03F35 /* DownloadMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3170048127A9504F00C03F35 /* DownloadMocks.swift */; };
317045C02858C6B90016ED1F /* AutofillInterfaceEmailTruncatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317045BF2858C6B90016ED1F /* AutofillInterfaceEmailTruncatorTests.swift */; };
+ 317CA3432CFF82E100F88848 /* SettingsAIChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317CA3422CFF82DB00F88848 /* SettingsAIChatView.swift */; };
+ 317DF6082D01E7B900DE0145 /* RoundedPageSheetContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317DF6072D01E7B900DE0145 /* RoundedPageSheetContainerViewController.swift */; };
+ 317DF60B2D01E7D600DE0145 /* RoundedPageSheetPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317DF60A2D01E7D600DE0145 /* RoundedPageSheetPresentationAnimator.swift */; };
317F5F982C94A9EB0081666F /* MarketplaceAdPostbackStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F5F972C94A9EB0081666F /* MarketplaceAdPostbackStorage.swift */; };
31860A5B2C57ED2D005561F5 /* DuckPlayerStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31860A5A2C57ED2D005561F5 /* DuckPlayerStorage.swift */; };
31951E8E2823003200CAF535 /* AutofillLoginDetailsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31951E8D2823003200CAF535 /* AutofillLoginDetailsHeaderView.swift */; };
- 319A371028299A850079FBCE /* PasswordHider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319A370F28299A850079FBCE /* PasswordHider.swift */; };
319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319A37142829A55F0079FBCE /* AutofillListItemTableViewCell.swift */; };
319A37172829C8AD0079FBCE /* UITableViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319A37162829C8AD0079FBCE /* UITableViewExtension.swift */; };
31A42564285A09E800049386 /* FaviconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A42563285A09E800049386 /* FaviconView.swift */; };
@@ -193,7 +199,7 @@
31DE43C42C2C60E800F8C51F /* DuckPlayerModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DE43C32C2C60E800F8C51F /* DuckPlayerModalPresenter.swift */; };
31DE43C62C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json in Resources */ = {isa = PBXBuildFile; fileRef = 31DE43C52C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json */; };
31E69A63280F4CB600478327 /* DuckUI in Frameworks */ = {isa = PBXBuildFile; productRef = 31E69A62280F4CB600478327 /* DuckUI */; };
- 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */; };
+ 31E77B272D038BBC006F1C9F /* OmnibarAccessoryHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E77B262D038BB9006F1C9F /* OmnibarAccessoryHandlerTests.swift */; };
3712091E2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3712091D2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift */; };
372A0FF02B2389590033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */; };
373608902ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3736088F2ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift */; };
@@ -284,6 +290,9 @@
56D060262C359D2E003BAEB5 /* ContextualOnboardingDialogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D060252C359D2E003BAEB5 /* ContextualOnboardingDialogs.swift */; };
56D060282C380D83003BAEB5 /* OnboardingSuggestedSearchesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D060272C380D83003BAEB5 /* OnboardingSuggestedSearchesProvider.swift */; };
56D0602D2C383FD2003BAEB5 /* OnboardingSuggestedSearchesProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D0602C2C383FD2003BAEB5 /* OnboardingSuggestedSearchesProviderTests.swift */; };
+ 56D7792C2CFF476800B619EF /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; };
+ 56D7793A2CFFC7E800B619EF /* PixelExperimentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 56D779392CFFC7E800B619EF /* PixelExperimentKit */; };
+ 56D7793C2CFFC7E800B619EF /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 56D7793B2CFFC7E800B619EF /* PixelKit */; };
56D8556A2BEA9169009F9698 /* CurrentDateProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D855692BEA9169009F9698 /* CurrentDateProviding.swift */; };
56D8556C2BEA91C4009F9698 /* SyncAlertsPresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D8556B2BEA91C4009F9698 /* SyncAlertsPresenting.swift */; };
6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */; };
@@ -321,6 +330,7 @@
6F7FB8E32C660BF300867DA7 /* DailyPixelFiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8E22C660BF300867DA7 /* DailyPixelFiring.swift */; };
6F7FB8E52C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8E42C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift */; };
6F7FB8E72C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8E62C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift */; };
+ 6F8348E32D01E401005872E3 /* AlternateAppIcons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6F8348E22D01E401005872E3 /* AlternateAppIcons.xcassets */; };
6F8496412BC3D8EE00ADA54E /* OnboardingButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8496402BC3D8EE00ADA54E /* OnboardingButtonsView.swift */; };
6F934F862C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F934F852C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift */; };
6F96FF102C2B128500162692 /* NewTabPageCustomizeButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F96FF0F2C2B128500162692 /* NewTabPageCustomizeButtonView.swift */; };
@@ -623,7 +633,6 @@
981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981FED75220464EF008488D7 /* AutoClearSettingsModel.swift */; };
9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9820EAF422613CD30089094D /* WebProgressWorker.swift */; };
9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9820FF4F2244FECC008D4782 /* UIScrollViewExtension.swift */; };
- 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */; };
982123502B6D233E00F08C57 /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9821234F2B6D233E00F08C57 /* UserSession.swift */; };
9825F9DB293F2E8700F220F2 /* BookmarksTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9825F9DA293F2E8700F220F2 /* BookmarksTestData.swift */; };
982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982686AC2600C0850011A8D6 /* ActionMessageView.swift */; };
@@ -739,8 +748,6 @@
98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3A1D7217B37010011A0D4 /* Theme.swift */; };
98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */; };
98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */; };
- 9F1061652C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */; };
- 9F1623092C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */; };
9F16230B2CA0F0190093C4FC /* DebouncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */; };
9F1798572CD2443F0073018B /* AddToDockPromoViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */; };
9F23B8012C2BC94400950875 /* OnboardingBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8002C2BC94400950875 /* OnboardingBackground.swift */; };
@@ -817,50 +824,6 @@
AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D854623D9E88E00788410 /* AppIconSettingsCell.swift */; };
AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D854823DA1DFB00788410 /* AppIcon.swift */; };
AA4D6A6A23DB87B1007E8790 /* AppIconManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D6A6923DB87B1007E8790 /* AppIconManager.swift */; };
- AA4D6A8C23DE49A5007E8790 /* AppIconBlack40x40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A8223DE49A4007E8790 /* AppIconBlack40x40@2x.png */; };
- AA4D6A8D23DE49A5007E8790 /* AppIconBlack40x40@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A8323DE49A4007E8790 /* AppIconBlack40x40@3x.png */; };
- AA4D6A8E23DE49A5007E8790 /* AppIconBlack60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A8423DE49A4007E8790 /* AppIconBlack60x60@2x.png */; };
- AA4D6A8F23DE49A5007E8790 /* AppIconBlack29x29@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A8523DE49A4007E8790 /* AppIconBlack29x29@3x.png */; };
- AA4D6A9123DE49A5007E8790 /* AppIconBlack60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A8723DE49A5007E8790 /* AppIconBlack60x60@3x.png */; };
- AA4D6A9323DE49A5007E8790 /* AppIconBlack76x76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A8923DE49A5007E8790 /* AppIconBlack76x76@2x.png */; };
- AA4D6A9423DE49A5007E8790 /* AppIconBlack29x29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A8A23DE49A5007E8790 /* AppIconBlack29x29@2x.png */; };
- AA4D6AA123DE4CC4007E8790 /* AppIconBlue60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A9723DE4CC3007E8790 /* AppIconBlue60x60@3x.png */; };
- AA4D6AA223DE4CC4007E8790 /* AppIconBlue76x76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A9823DE4CC3007E8790 /* AppIconBlue76x76@2x.png */; };
- AA4D6AA323DE4CC4007E8790 /* AppIconBlue40x40@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A9923DE4CC3007E8790 /* AppIconBlue40x40@3x.png */; };
- AA4D6AA423DE4CC4007E8790 /* AppIconBlue29x29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A9A23DE4CC3007E8790 /* AppIconBlue29x29@2x.png */; };
- AA4D6AA523DE4CC4007E8790 /* AppIconBlue29x29@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A9B23DE4CC3007E8790 /* AppIconBlue29x29@3x.png */; };
- AA4D6AA723DE4CC4007E8790 /* AppIconBlue60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A9D23DE4CC4007E8790 /* AppIconBlue60x60@2x.png */; };
- AA4D6AA823DE4CC4007E8790 /* AppIconBlue40x40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6A9E23DE4CC4007E8790 /* AppIconBlue40x40@2x.png */; };
- AA4D6AB823DE4D15007E8790 /* AppIconYellow29x29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AAE23DE4D14007E8790 /* AppIconYellow29x29@2x.png */; };
- AA4D6AB923DE4D15007E8790 /* AppIconYellow29x29@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AAF23DE4D14007E8790 /* AppIconYellow29x29@3x.png */; };
- AA4D6ABB23DE4D15007E8790 /* AppIconYellow40x40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AB123DE4D14007E8790 /* AppIconYellow40x40@2x.png */; };
- AA4D6ABC23DE4D15007E8790 /* AppIconYellow60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AB223DE4D14007E8790 /* AppIconYellow60x60@3x.png */; };
- AA4D6ABD23DE4D15007E8790 /* AppIconYellow60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AB323DE4D15007E8790 /* AppIconYellow60x60@2x.png */; };
- AA4D6ABF23DE4D15007E8790 /* AppIconYellow40x40@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AB523DE4D15007E8790 /* AppIconYellow40x40@3x.png */; };
- AA4D6AC023DE4D15007E8790 /* AppIconYellow76x76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AB623DE4D15007E8790 /* AppIconYellow76x76@2x.png */; };
- AA4D6ACC23DE4D27007E8790 /* AppIconPurple60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AC223DE4D26007E8790 /* AppIconPurple60x60@2x.png */; };
- AA4D6ACD23DE4D27007E8790 /* AppIconPurple29x29@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AC323DE4D26007E8790 /* AppIconPurple29x29@3x.png */; };
- AA4D6ACE23DE4D27007E8790 /* AppIconPurple60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AC423DE4D26007E8790 /* AppIconPurple60x60@3x.png */; };
- AA4D6ACF23DE4D27007E8790 /* AppIconPurple76x76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AC523DE4D26007E8790 /* AppIconPurple76x76@2x.png */; };
- AA4D6AD123DE4D27007E8790 /* AppIconPurple40x40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AC723DE4D26007E8790 /* AppIconPurple40x40@2x.png */; };
- AA4D6AD323DE4D27007E8790 /* AppIconPurple29x29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AC923DE4D26007E8790 /* AppIconPurple29x29@2x.png */; };
- AA4D6AD423DE4D27007E8790 /* AppIconPurple40x40@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6ACA23DE4D26007E8790 /* AppIconPurple40x40@3x.png */; };
- AA4D6AE123DE4D33007E8790 /* AppIconGreen76x76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AD723DE4D32007E8790 /* AppIconGreen76x76@2x.png */; };
- AA4D6AE223DE4D33007E8790 /* AppIconGreen40x40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AD823DE4D32007E8790 /* AppIconGreen40x40@2x.png */; };
- AA4D6AE323DE4D33007E8790 /* AppIconGreen60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AD923DE4D32007E8790 /* AppIconGreen60x60@2x.png */; };
- AA4D6AE423DE4D33007E8790 /* AppIconGreen40x40@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6ADA23DE4D32007E8790 /* AppIconGreen40x40@3x.png */; };
- AA4D6AE623DE4D33007E8790 /* AppIconGreen60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6ADC23DE4D33007E8790 /* AppIconGreen60x60@3x.png */; };
- AA4D6AE723DE4D33007E8790 /* AppIconGreen29x29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6ADD23DE4D33007E8790 /* AppIconGreen29x29@2x.png */; };
- AA4D6AE923DE4D33007E8790 /* AppIconGreen29x29@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6ADF23DE4D33007E8790 /* AppIconGreen29x29@3x.png */; };
- AA4D6AF623DF0312007E8790 /* AppIconRed60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AF423DF0312007E8790 /* AppIconRed60x60@3x.png */; };
- AA4D6AF723DF0312007E8790 /* AppIconRed60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AF523DF0312007E8790 /* AppIconRed60x60@2x.png */; };
- AA4D6AFA23DF0CF6007E8790 /* AppIconRed29x29@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AF823DF0CF5007E8790 /* AppIconRed29x29@3x.png */; };
- AA4D6AFB23DF0CF6007E8790 /* AppIconRed29x29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA4D6AF923DF0CF6007E8790 /* AppIconRed29x29@2x.png */; };
- AAF2E28123E0495400962AF8 /* AppIconBlack83.5x83.5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AAF2E28023E0495400962AF8 /* AppIconBlack83.5x83.5@2x.png */; };
- AAF2E28323E0495E00962AF8 /* AppIconBlue83.5x83.5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AAF2E28223E0495E00962AF8 /* AppIconBlue83.5x83.5@2x.png */; };
- AAF2E28523E0496F00962AF8 /* AppIconGreen83.5x83.5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AAF2E28423E0496F00962AF8 /* AppIconGreen83.5x83.5@2x.png */; };
- AAF2E28723E0498200962AF8 /* AppIconPurple83.5x83.5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AAF2E28623E0498100962AF8 /* AppIconPurple83.5x83.5@2x.png */; };
- AAF2E28B23E049DF00962AF8 /* AppIconYellow83.5x83.5@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AAF2E28A23E049DF00962AF8 /* AppIconYellow83.5x83.5@2x.png */; };
B603974929C19F6F00902A34 /* Assertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B603974829C19F6F00902A34 /* Assertions.swift */; };
B609D5522862EAFF0088CAC2 /* InlineWKDownloadDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B609D5512862EAFF0088CAC2 /* InlineWKDownloadDelegate.swift */; };
B60DFF072872B64B0061E7C2 /* JSAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60DFF062872B64B0061E7C2 /* JSAlertController.swift */; };
@@ -910,6 +873,7 @@
C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */; };
C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */; };
C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */; };
+ C13C076C2D00A6B7006386CF /* VaultCredentialManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13C076B2D00A6B7006386CF /* VaultCredentialManager.swift */; };
C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */; };
C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */; };
C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */; };
@@ -930,6 +894,7 @@
C1641EB12BC2F52B0012607A /* ImportPasswordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1641EB02BC2F52B0012607A /* ImportPasswordsView.swift */; };
C1641EB32BC2F53C0012607A /* ImportPasswordsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1641EB22BC2F53C0012607A /* ImportPasswordsViewModel.swift */; };
C174CE602BD6A6CE00AED2EA /* MockDDGSyncing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C185ED652BD43A5500BAE9DC /* MockDDGSyncing.swift */; };
+ C177D9F62CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D9F52CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift */; };
C17B59592A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59562A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift */; };
C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59572A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift */; };
C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59582A03AAD30055F2D1 /* PasswordGenerationPromptView.swift */; };
@@ -944,6 +909,7 @@
C1935A222C89CA9F001AD72D /* AutofillSurveyManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1935A212C89CA9F001AD72D /* AutofillSurveyManagerTests.swift */; };
C1935A242C89CC6D001AD72D /* AutofillHeaderViewFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1935A232C89CC6D001AD72D /* AutofillHeaderViewFactoryTests.swift */; };
C1963863283794A000298D4D /* BookmarksCachingSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1963862283794A000298D4D /* BookmarksCachingSearch.swift */; };
+ C19D90D12CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19D90D02CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift */; };
C1B7B51C28941E980098FD6A /* HomeMessageViewModelBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B7B51B28941E980098FD6A /* HomeMessageViewModelBuilder.swift */; };
C1B7B52528941F2A0098FD6A /* RemoteMessagingClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B7B52128941F2A0098FD6A /* RemoteMessagingClient.swift */; };
C1B7B52D2894469D0098FD6A /* DefaultVariantManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B7B52C2894469D0098FD6A /* DefaultVariantManager.swift */; };
@@ -952,13 +918,48 @@
C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BF0BA429B63D7200482B73 /* AutofillLoginPromptHelper.swift */; };
C1BF0BA929B63E2200482B73 /* AutofillLoginPromptViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BF0BA729B63E1A00482B73 /* AutofillLoginPromptViewModelTests.swift */; };
C1BF26152C74D10F00F6405E /* SyncPromoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BF26142C74D10F00F6405E /* SyncPromoManager.swift */; };
+ C1C1FF452D085A280017ACCE /* CredentialProviderListDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C1FF412D085A280017ACCE /* CredentialProviderListDetailsView.swift */; };
+ C1C1FF462D085A280017ACCE /* CredentialProviderListDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C1FF422D085A280017ACCE /* CredentialProviderListDetailsViewController.swift */; };
+ C1C1FF472D085A280017ACCE /* CredentialProviderListDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C1FF432D085A280017ACCE /* CredentialProviderListDetailsViewModel.swift */; };
+ C1C1FF4A2D085A4C0017ACCE /* AutofillInterfaceEmailTruncator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C1FF482D085A4C0017ACCE /* AutofillInterfaceEmailTruncator.swift */; };
+ C1C1FF4B2D085A4C0017ACCE /* PasswordHider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C1FF492D085A4C0017ACCE /* PasswordHider.swift */; };
+ C1C1FF502D085AD70017ACCE /* ActionMessageView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1C1FF4D2D085AD70017ACCE /* ActionMessageView.xib */; };
+ C1C1FF512D085AD70017ACCE /* FaviconHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C1FF4E2D085AD70017ACCE /* FaviconHelper.swift */; };
+ C1C1FF522D085AD70017ACCE /* ActionMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C1FF4C2D085AD70017ACCE /* ActionMessageView.swift */; };
+ C1C1FF532D085AD70017ACCE /* NibLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C1FF4F2D085AD70017ACCE /* NibLoading.swift */; };
+ C1CAAA6A2CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA692CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift */; };
+ C1CAAA712CF8BC0B00C37EE6 /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA702CF8BC0B00C37EE6 /* UIViewControllerExtension.swift */; };
+ C1CAAA732CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA722CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift */; };
+ C1CAAA782CF8BDF200C37EE6 /* UserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA772CF8BDF200C37EE6 /* UserText.swift */; };
+ C1CAAA7A2CF8BE0200C37EE6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1CAAA792CF8BE0200C37EE6 /* Assets.xcassets */; };
+ C1CAAA7B2CF8C8ED00C37EE6 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F143C2E41E4A4CD400CFDE3A /* Core.framework */; };
+ C1CAAA812CF8C8F400C37EE6 /* DuckUI in Frameworks */ = {isa = PBXBuildFile; productRef = C1CAAA802CF8C8F400C37EE6 /* DuckUI */; };
+ C1CAAA832CF8C8FF00C37EE6 /* DesignResourcesKit in Frameworks */ = {isa = PBXBuildFile; productRef = C1CAAA822CF8C8FF00C37EE6 /* DesignResourcesKit */; };
+ C1CAAA852CF8C9EA00C37EE6 /* UIResponderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */; };
+ C1CAAA882CF9FFE100C37EE6 /* CredentialProviderListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA872CF9FFE100C37EE6 /* CredentialProviderListViewController.swift */; };
+ C1CAAA8A2CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA892CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift */; };
+ C1CAAA922CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA912CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift */; };
+ C1CAAA9A2CFCAD3E00C37EE6 /* AutofillLoginItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */; };
+ C1CAAA9C2CFCB39800C37EE6 /* AutofillLoginListSorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */; };
+ C1CAAA9E2CFCB78700C37EE6 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA9D2CFCB78700C37EE6 /* UIColorExtension.swift */; };
+ C1CAAAA02CFCB7C200C37EE6 /* UImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA9F2CFCB7C200C37EE6 /* UImageExtension.swift */; };
+ C1CAAAA32CFCBBBD00C37EE6 /* UserAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAA22CFCBBBD00C37EE6 /* UserAuthenticator.swift */; };
+ C1CAAAA62CFCBD7900C37EE6 /* LockScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAA52CFCBD7900C37EE6 /* LockScreenView.swift */; };
+ C1CAAAA82CFCBE4800C37EE6 /* EmptySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAA72CFCBE4800C37EE6 /* EmptySearchView.swift */; };
+ C1CAAAAA2CFCC13E00C37EE6 /* SecureVaultReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAA92CFCC13E00C37EE6 /* SecureVaultReporter.swift */; };
+ C1CAAAAC2CFCC91D00C37EE6 /* EmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAAB2CFCC91D00C37EE6 /* EmptyView.swift */; };
C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */; };
C1CDA31E2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */; };
C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */; };
C1D21E2F293A599C006E5A05 /* AutofillLoginSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D21E2E293A599C006E5A05 /* AutofillLoginSessionTests.swift */; };
C1E42C7B2C5CD8AE00509204 /* AutofillCredentialsDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E42C7A2C5CD8AD00509204 /* AutofillCredentialsDebugViewController.swift */; };
+ C1E4E9A62D0861AD00AA39AF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1E4E9A42D0861AD00AA39AF /* InfoPlist.strings */; };
+ C1E4E9A92D0861AD00AA39AF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1E4E9A72D0861AD00AA39AF /* Localizable.strings */; };
C1EA86602C74CB6C00E8604D /* SyncPromoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EA865F2C74CB6C00E8604D /* SyncPromoView.swift */; };
C1EA86622C74CB8B00E8604D /* SyncPromoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EA86612C74CB8B00E8604D /* SyncPromoViewModel.swift */; };
+ C1EF5B232CC0457B002980E6 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1EF5B222CC0457B002980E6 /* AuthenticationServices.framework */; };
+ C1EF5B262CC0457B002980E6 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */; };
+ C1EF5B2E2CC0457B002980E6 /* AutofillCredentialProvider.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C1EF5B212CC0457B002980E6 /* AutofillCredentialProvider.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C42A6924000032057B /* EmailAddressPromptView.swift */; };
C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */; };
C1F341C92A6926920032057B /* EmailAddressPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */; };
@@ -973,6 +974,7 @@
CB2A7EEF283D185100885F67 /* RulesCompilationMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EEE283D185100885F67 /* RulesCompilationMonitor.swift */; };
CB2A7EF128410DF700885F67 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EF028410DF700885F67 /* PixelEvent.swift */; };
CB2A7EF4285383B300885F67 /* AppLastCompiledRulesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */; };
+ CB3C78912D08484800A7E4ED /* InactiveBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB3C78902D08483F00A7E4ED /* InactiveBackground.swift */; };
CB48D3332B90CE9F00631D8B /* PageRefreshStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */; };
CB4FA44E2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */; };
CB5516D0286500290079B175 /* TrackerRadarIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */; };
@@ -1039,7 +1041,6 @@
D664C7C92B289AA200CBFA76 /* AsyncHeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AF2B289AA000CBFA76 /* AsyncHeadlessWebView.swift */; };
D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */; };
D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; };
- D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; };
D668D9252B693778008E2FF2 /* SubscriptionITPView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9242B693778008E2FF2 /* SubscriptionITPView.swift */; };
D668D9272B6937D2008E2FF2 /* SubscriptionITPViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */; };
D668D9292B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */; };
@@ -1055,7 +1056,6 @@
D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */; };
D6B67A122C332B6E002122EB /* DuckPlayerMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B67A112C332B6E002122EB /* DuckPlayerMocks.swift */; };
D6B9E8D22CDA4420002B640C /* DuckPlayerOverlayUsagePixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9E8D12CDA4418002B640C /* DuckPlayerOverlayUsagePixels.swift */; };
- D6B9E8D42CDA8375002B640C /* DuckPlayerOverlayUsagePixelsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9E8D32CDA8369002B640C /* DuckPlayerOverlayUsagePixelsTests.swift */; };
D6BC8ACB2C5AA3860025375B /* DuckPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = D6BC8ACA2C5AA3860025375B /* DuckPlayer */; };
D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; };
D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; };
@@ -1215,7 +1215,6 @@
F486D3382506A225002D07D7 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F486D3372506A225002D07D7 /* OHHTTPStubsSwift */; };
F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B0B78B252CAFF700830156 /* OnboardingWidgetsViewController.swift */; };
F4B0B796252CB35700830156 /* OnboardingWidgetsDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B0B795252CB35700830156 /* OnboardingWidgetsDetailsViewController.swift */; };
- F4C9FBF528340DDA002281CC /* AutofillInterfaceEmailTruncator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C9FBF428340DDA002281CC /* AutofillInterfaceEmailTruncator.swift */; };
F4CE6D1B257EA33C00D0A6AA /* FireButtonAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE6D1A257EA33C00D0A6AA /* FireButtonAnimator.swift */; };
F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D7220F26F29A70007D6193 /* BookmarkDetailsCell.swift */; };
F4D7F634298C00C3006C3AE9 /* FindInPageIOSJSSupport in Frameworks */ = {isa = PBXBuildFile; productRef = F4D7F633298C00C3006C3AE9 /* FindInPageIOSJSSupport */; };
@@ -1323,6 +1322,20 @@
remoteGlobalIDString = B6DFE6CE2BC7E47500A9CE59;
remoteInfo = SwiftLintToolBundle;
};
+ C1CAAA7D2CF8C8ED00C37EE6 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = F143C2E31E4A4CD400CFDE3A;
+ remoteInfo = Core;
+ };
+ C1EF5B2C2CC0457B002980E6 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = C1EF5B202CC0457B002980E6;
+ remoteInfo = AutofillCredentialProvider;
+ };
F143C2E91E4A4CD400CFDE3A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */;
@@ -1339,6 +1352,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
+ C1EF5B2E2CC0457B002980E6 /* AutofillCredentialProvider.appex in Embed App Extensions */,
85482D942462DCD100EDEDD1 /* OpenAction.appex in Embed App Extensions */,
8512EA5D24ED30D30073EE19 /* WidgetsExtension.appex in Embed App Extensions */,
8390447620BDCE10006461CD /* ShareExtension.appex in Embed App Extensions */,
@@ -1498,6 +1512,8 @@
310D09202799FD1A00DC0060 /* MIMEType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MIMEType.swift; sourceTree = ""; };
310E79BC2949CAA5007C49E8 /* FireButtonReferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FireButtonReferenceTests.swift; sourceTree = ""; };
310ECFDC282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableAutofillSettingsTableViewCell.swift; sourceTree = ""; };
+ 310EEA2E2CFFCDBF0043CA1A /* AIChatSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatSettingsTests.swift; sourceTree = ""; };
+ 311711902D00E53A0063AC3D /* OmnibarAccessoryHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnibarAccessoryHandling.swift; sourceTree = ""; };
311BD1AC2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillItemsEmptyView.swift; sourceTree = ""; };
311BD1AE2836BB4200AEF6C1 /* AutofillItemsLockedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillItemsLockedView.swift; sourceTree = ""; };
311BD1B02836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListAuthenticator.swift; sourceTree = ""; };
@@ -1518,18 +1534,22 @@
3157B43727F4C8490042D3D7 /* FaviconsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconsHelper.swift; sourceTree = ""; };
31584615281AFB46004ADB8B /* AutofillLoginDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginDetailsViewController.swift; sourceTree = ""; };
31584619281B08F5004ADB8B /* AutofillLoginListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListViewModel.swift; sourceTree = ""; };
+ 315C77802CFA414400699683 /* AIChat */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = AIChat; sourceTree = ""; };
3161D13127AC161B00285CF6 /* DownloadMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadMetadata.swift; sourceTree = ""; };
31669B9928020A460071CC18 /* SaveLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveLoginViewModel.swift; sourceTree = ""; };
316790E42C9352190090B0A2 /* MarketplaceAdPostbackManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplaceAdPostbackManagerTests.swift; sourceTree = ""; };
316931D627BD10BB0095F5ED /* SaveToDownloadsAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveToDownloadsAlert.swift; sourceTree = ""; };
316931D827BD22A80095F5ED /* DownloadActionMessageViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadActionMessageViewHelper.swift; sourceTree = ""; };
+ 316AA4592CF8E31F00A2ED28 /* AIChatSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatSettings.swift; sourceTree = ""; };
3170048127A9504F00C03F35 /* DownloadMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadMocks.swift; sourceTree = ""; };
317045BF2858C6B90016ED1F /* AutofillInterfaceEmailTruncatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillInterfaceEmailTruncatorTests.swift; sourceTree = ""; };
31794BFF2821DFB600F18633 /* DuckUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = DuckUI; sourceTree = ""; };
+ 317CA3422CFF82DB00F88848 /* SettingsAIChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAIChatView.swift; sourceTree = ""; };
+ 317DF6072D01E7B900DE0145 /* RoundedPageSheetContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedPageSheetContainerViewController.swift; sourceTree = ""; };
+ 317DF60A2D01E7D600DE0145 /* RoundedPageSheetPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedPageSheetPresentationAnimator.swift; sourceTree = ""; };
317F5F972C94A9EB0081666F /* MarketplaceAdPostbackStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplaceAdPostbackStorage.swift; sourceTree = ""; };
31860A5A2C57ED2D005561F5 /* DuckPlayerStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuckPlayerStorage.swift; sourceTree = ""; };
31951E8D2823003200CAF535 /* AutofillLoginDetailsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginDetailsHeaderView.swift; sourceTree = ""; };
- 319A370F28299A850079FBCE /* PasswordHider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordHider.swift; sourceTree = ""; };
319A37142829A55F0079FBCE /* AutofillListItemTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillListItemTableViewCell.swift; sourceTree = ""; };
319A37162829C8AD0079FBCE /* UITableViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewExtension.swift; sourceTree = ""; };
31A42563285A09E800049386 /* FaviconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconView.swift; sourceTree = ""; };
@@ -1554,7 +1574,7 @@
31DE43C12C2C480D00F8C51F /* DuckPlayerFeaturePresentationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerFeaturePresentationView.swift; sourceTree = ""; };
31DE43C32C2C60E800F8C51F /* DuckPlayerModalPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerModalPresenter.swift; sourceTree = ""; };
31DE43C52C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "DuckPlayer-ModalAnimation.json"; sourceTree = ""; };
- 31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListItemViewModel.swift; sourceTree = ""; };
+ 31E77B262D038BB9006F1C9F /* OmnibarAccessoryHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnibarAccessoryHandlerTests.swift; sourceTree = ""; };
3712091D2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteMessagingStoreErrorHandling.swift; sourceTree = ""; };
372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncMetricsEventsHandler.swift; sourceTree = ""; };
3736088F2ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesDisplayModeStorage.swift; sourceTree = ""; };
@@ -1666,6 +1686,7 @@
6F7FB8E22C660BF300867DA7 /* DailyPixelFiring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixelFiring.swift; sourceTree = ""; };
6F7FB8E42C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageShortcutsSettingsModelTests.swift; sourceTree = ""; };
6F7FB8E62C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSectionsSettingsModelTests.swift; sourceTree = ""; };
+ 6F8348E22D01E401005872E3 /* AlternateAppIcons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = AlternateAppIcons.xcassets; sourceTree = ""; };
6F8496402BC3D8EE00ADA54E /* OnboardingButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingButtonsView.swift; sourceTree = ""; };
6F934F852C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSettingsPersistentStorageTests.swift; sourceTree = ""; };
6F96FF0F2C2B128500162692 /* NewTabPageCustomizeButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageCustomizeButtonView.swift; sourceTree = ""; };
@@ -2017,7 +2038,6 @@
9820A5D522B1C0B20024E37C /* DDG Trace.tracetemplate */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "DDG Trace.tracetemplate"; sourceTree = ""; };
9820EAF422613CD30089094D /* WebProgressWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebProgressWorker.swift; sourceTree = ""; };
9820FF4F2244FECC008D4782 /* UIScrollViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollViewExtension.swift; sourceTree = ""; };
- 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAuthenticator.swift; sourceTree = ""; };
9821234F2B6D233E00F08C57 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; };
9825F9D7293F2DE900F220F2 /* PerformanceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PerformanceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9825F9DA293F2E8700F220F2 /* BookmarksTestData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksTestData.swift; sourceTree = ""; };
@@ -2579,8 +2599,6 @@
98F3A1D7217B37010011A0D4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; };
98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesLists.swift; sourceTree = ""; };
98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemableNavigationController.swift; sourceTree = ""; };
- 9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultVariantManager+Onboarding.swift"; sourceTree = ""; };
- 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultVariantManagerOnboardingTests.swift; sourceTree = ""; };
9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncerTests.swift; sourceTree = ""; };
9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToDockPromoViewModelTests.swift; sourceTree = ""; };
9F23B8002C2BC94400950875 /* OnboardingBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackground.swift; sourceTree = ""; };
@@ -2655,50 +2673,6 @@
AA3D854623D9E88E00788410 /* AppIconSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconSettingsCell.swift; sourceTree = ""; };
AA3D854823DA1DFB00788410 /* AppIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIcon.swift; sourceTree = ""; };
AA4D6A6923DB87B1007E8790 /* AppIconManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconManager.swift; sourceTree = ""; };
- AA4D6A8223DE49A4007E8790 /* AppIconBlack40x40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlack40x40@2x.png"; sourceTree = ""; };
- AA4D6A8323DE49A4007E8790 /* AppIconBlack40x40@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlack40x40@3x.png"; sourceTree = ""; };
- AA4D6A8423DE49A4007E8790 /* AppIconBlack60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlack60x60@2x.png"; sourceTree = ""; };
- AA4D6A8523DE49A4007E8790 /* AppIconBlack29x29@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlack29x29@3x.png"; sourceTree = ""; };
- AA4D6A8723DE49A5007E8790 /* AppIconBlack60x60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlack60x60@3x.png"; sourceTree = ""; };
- AA4D6A8923DE49A5007E8790 /* AppIconBlack76x76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlack76x76@2x.png"; sourceTree = ""; };
- AA4D6A8A23DE49A5007E8790 /* AppIconBlack29x29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlack29x29@2x.png"; sourceTree = ""; };
- AA4D6A9723DE4CC3007E8790 /* AppIconBlue60x60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlue60x60@3x.png"; sourceTree = ""; };
- AA4D6A9823DE4CC3007E8790 /* AppIconBlue76x76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlue76x76@2x.png"; sourceTree = ""; };
- AA4D6A9923DE4CC3007E8790 /* AppIconBlue40x40@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlue40x40@3x.png"; sourceTree = ""; };
- AA4D6A9A23DE4CC3007E8790 /* AppIconBlue29x29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlue29x29@2x.png"; sourceTree = ""; };
- AA4D6A9B23DE4CC3007E8790 /* AppIconBlue29x29@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlue29x29@3x.png"; sourceTree = ""; };
- AA4D6A9D23DE4CC4007E8790 /* AppIconBlue60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlue60x60@2x.png"; sourceTree = ""; };
- AA4D6A9E23DE4CC4007E8790 /* AppIconBlue40x40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlue40x40@2x.png"; sourceTree = ""; };
- AA4D6AAE23DE4D14007E8790 /* AppIconYellow29x29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconYellow29x29@2x.png"; sourceTree = ""; };
- AA4D6AAF23DE4D14007E8790 /* AppIconYellow29x29@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconYellow29x29@3x.png"; sourceTree = ""; };
- AA4D6AB123DE4D14007E8790 /* AppIconYellow40x40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconYellow40x40@2x.png"; sourceTree = ""; };
- AA4D6AB223DE4D14007E8790 /* AppIconYellow60x60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconYellow60x60@3x.png"; sourceTree = ""; };
- AA4D6AB323DE4D15007E8790 /* AppIconYellow60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconYellow60x60@2x.png"; sourceTree = ""; };
- AA4D6AB523DE4D15007E8790 /* AppIconYellow40x40@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconYellow40x40@3x.png"; sourceTree = ""; };
- AA4D6AB623DE4D15007E8790 /* AppIconYellow76x76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconYellow76x76@2x.png"; sourceTree = ""; };
- AA4D6AC223DE4D26007E8790 /* AppIconPurple60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconPurple60x60@2x.png"; sourceTree = ""; };
- AA4D6AC323DE4D26007E8790 /* AppIconPurple29x29@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconPurple29x29@3x.png"; sourceTree = ""; };
- AA4D6AC423DE4D26007E8790 /* AppIconPurple60x60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconPurple60x60@3x.png"; sourceTree = ""; };
- AA4D6AC523DE4D26007E8790 /* AppIconPurple76x76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconPurple76x76@2x.png"; sourceTree = ""; };
- AA4D6AC723DE4D26007E8790 /* AppIconPurple40x40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconPurple40x40@2x.png"; sourceTree = ""; };
- AA4D6AC923DE4D26007E8790 /* AppIconPurple29x29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconPurple29x29@2x.png"; sourceTree = ""; };
- AA4D6ACA23DE4D26007E8790 /* AppIconPurple40x40@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconPurple40x40@3x.png"; sourceTree = ""; };
- AA4D6AD723DE4D32007E8790 /* AppIconGreen76x76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconGreen76x76@2x.png"; sourceTree = ""; };
- AA4D6AD823DE4D32007E8790 /* AppIconGreen40x40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconGreen40x40@2x.png"; sourceTree = ""; };
- AA4D6AD923DE4D32007E8790 /* AppIconGreen60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconGreen60x60@2x.png"; sourceTree = ""; };
- AA4D6ADA23DE4D32007E8790 /* AppIconGreen40x40@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconGreen40x40@3x.png"; sourceTree = ""; };
- AA4D6ADC23DE4D33007E8790 /* AppIconGreen60x60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconGreen60x60@3x.png"; sourceTree = ""; };
- AA4D6ADD23DE4D33007E8790 /* AppIconGreen29x29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconGreen29x29@2x.png"; sourceTree = ""; };
- AA4D6ADF23DE4D33007E8790 /* AppIconGreen29x29@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconGreen29x29@3x.png"; sourceTree = ""; };
- AA4D6AF423DF0312007E8790 /* AppIconRed60x60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconRed60x60@3x.png"; sourceTree = ""; };
- AA4D6AF523DF0312007E8790 /* AppIconRed60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconRed60x60@2x.png"; sourceTree = ""; };
- AA4D6AF823DF0CF5007E8790 /* AppIconRed29x29@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconRed29x29@3x.png"; sourceTree = ""; };
- AA4D6AF923DF0CF6007E8790 /* AppIconRed29x29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconRed29x29@2x.png"; sourceTree = ""; };
- AAF2E28023E0495400962AF8 /* AppIconBlack83.5x83.5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlack83.5x83.5@2x.png"; sourceTree = ""; };
- AAF2E28223E0495E00962AF8 /* AppIconBlue83.5x83.5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconBlue83.5x83.5@2x.png"; sourceTree = ""; };
- AAF2E28423E0496F00962AF8 /* AppIconGreen83.5x83.5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconGreen83.5x83.5@2x.png"; sourceTree = ""; };
- AAF2E28623E0498100962AF8 /* AppIconPurple83.5x83.5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconPurple83.5x83.5@2x.png"; sourceTree = ""; };
- AAF2E28A23E049DF00962AF8 /* AppIconYellow83.5x83.5@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIconYellow83.5x83.5@2x.png"; sourceTree = ""; };
B603974829C19F6F00902A34 /* Assertions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assertions.swift; sourceTree = ""; };
B609D5512862EAFF0088CAC2 /* InlineWKDownloadDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineWKDownloadDelegate.swift; sourceTree = ""; };
B60DFF062872B64B0061E7C2 /* JSAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertController.swift; sourceTree = ""; };
@@ -2741,13 +2715,28 @@
BDFF03242BA3D92E00F324C9 /* NetworkProtectionFeatureVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibilityTests.swift; sourceTree = ""; };
C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillViews.swift; sourceTree = ""; };
C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkOrFolderTests.swift; sourceTree = ""; };
+ C1193F602D08642900CB3239 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1193F612D08642900CB3239 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; };
+ C11C4D302D08648100288E85 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = ""; };
+ C11C4D312D08648100288E85 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; };
C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptView.swift; sourceTree = ""; };
C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewModel.swift; sourceTree = ""; };
C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewController.swift; sourceTree = ""; };
+ C12854D82D08636E00C8353F /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/InfoPlist.strings; sourceTree = ""; };
+ C12854D92D08636E00C8353F /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/Localizable.strings; sourceTree = ""; };
+ C129DF092D0862D7007AB046 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; };
+ C129DF0A2D0862D7007AB046 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; };
+ C132F5A32D0862B8000C81D0 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = ""; };
+ C132F5A42D0862B8000C81D0 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; };
C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillSettingStatus.swift; sourceTree = ""; };
+ C13C076B2D00A6B7006386CF /* VaultCredentialManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultCredentialManager.swift; sourceTree = ""; };
C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptView.swift; sourceTree = ""; };
C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptViewController.swift; sourceTree = ""; };
C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptViewModel.swift; sourceTree = ""; };
+ C1406D862D0862F30082CB50 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1406D872D0862F30082CB50 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; };
+ C1453E322D0863C80024449B /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1453E332D0863C80024449B /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; };
C14882D727F2011C00D59F0C /* BookmarksExporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksExporter.swift; sourceTree = ""; };
C14882D927F2011C00D59F0C /* BookmarksImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksImporter.swift; sourceTree = ""; };
C14882E127F20D9A00D59F0C /* BookmarksExporterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksExporterTests.swift; sourceTree = ""; };
@@ -2755,22 +2744,37 @@
C14882E527F20DAA00D59F0C /* HtmlTestDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HtmlTestDataLoader.swift; sourceTree = ""; };
C14882E627F20DAB00D59F0C /* TestDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDataLoader.swift; sourceTree = ""; };
C14882E927F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockBookmarksCoreDataStorage.swift; sourceTree = ""; };
+ C14D37D62D08649E00FCFC59 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; };
+ C14D37D72D08649E00FCFC59 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; };
C14D43002B45D6CD00ACA4DC /* AutofillDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillDebugViewController.swift; sourceTree = ""; };
C14E2F7629DE14EA002AC515 /* AutofillInterfaceUsernameTruncatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillInterfaceUsernameTruncatorTests.swift; sourceTree = ""; };
+ C1588FC42D08644800C9BE70 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1588FC52D08644800C9BE70 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; };
C158AC7A297AB5DC0008723A /* MockSecureVault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSecureVault.swift; sourceTree = ""; };
C159DF062A430B60007834BB /* EmailSignupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupViewController.swift; sourceTree = ""; };
C160544029D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillInterfaceUsernameTruncator.swift; sourceTree = ""; };
+ C163677A2D08638C001D1094 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; };
+ C163677B2D08638C001D1094 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; };
C1641EAE2BC2F5140012607A /* ImportPasswordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsViewController.swift; sourceTree = ""; };
C1641EB02BC2F52B0012607A /* ImportPasswordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsView.swift; sourceTree = ""; };
C1641EB22BC2F53C0012607A /* ImportPasswordsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsViewModel.swift; sourceTree = ""; };
+ C164F9472D0861D600BAE88E /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = ""; };
+ C164F9482D0861D600BAE88E /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; };
+ C174E08E2D08625300ACE1AF /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; };
+ C174E08F2D08625300ACE1AF /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; };
+ C177D9F52CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertControllerExtension.swift; sourceTree = ""; };
C17B59562A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptViewModel.swift; sourceTree = ""; };
C17B59572A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptViewController.swift; sourceTree = ""; };
C17B59582A03AAD30055F2D1 /* PasswordGenerationPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptView.swift; sourceTree = ""; };
+ C180CAFA2D0863E500ADB0FE /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/InfoPlist.strings; sourceTree = ""; };
+ C180CAFB2D0863E500ADB0FE /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; };
C1836CE02C359EC90016D057 /* AutofillBreakageReportCellContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillBreakageReportCellContentView.swift; sourceTree = ""; };
C1836CE42C35A0EA0016D057 /* AutofillBreakageReportTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillBreakageReportTableViewCell.swift; sourceTree = ""; };
C185ED602BD4329700BAE9DC /* ImportPasswordsStatusHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsStatusHandler.swift; sourceTree = ""; };
C185ED632BD438AF00BAE9DC /* ImportPasswordsStatusHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsStatusHandlerTests.swift; sourceTree = ""; };
C185ED652BD43A5500BAE9DC /* MockDDGSyncing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDDGSyncing.swift; sourceTree = ""; };
+ C18D7C412D08620D00FB3F87 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; };
+ C18D7C422D08620D00FB3F87 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; };
C18ED4392AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillSettingsEnableFooterView.swift; sourceTree = ""; };
C18ED43B2AB8364400BF3805 /* FileTextPreviewDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileTextPreviewDebugViewController.swift; sourceTree = ""; };
C1935A0D2C88D11D001AD72D /* AutofillSurveyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillSurveyView.swift; sourceTree = ""; };
@@ -2779,25 +2783,79 @@
C1935A212C89CA9F001AD72D /* AutofillSurveyManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillSurveyManagerTests.swift; sourceTree = ""; };
C1935A232C89CC6D001AD72D /* AutofillHeaderViewFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillHeaderViewFactoryTests.swift; sourceTree = ""; };
C1963862283794A000298D4D /* BookmarksCachingSearch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksCachingSearch.swift; sourceTree = ""; };
+ C19D90D02CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListSectionType.swift; sourceTree = ""; };
+ C1A001092D08635100372C87 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1A0010A2D08635100372C87 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = ""; };
C1B0F6412AB08BE9001EAF05 /* MockPrivacyConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrivacyConfiguration.swift; sourceTree = ""; };
+ C1B783DB2D0863110071C53B /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1B783DC2D0863110071C53B /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; };
C1B7B51B28941E980098FD6A /* HomeMessageViewModelBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModelBuilder.swift; sourceTree = ""; };
C1B7B52128941F2A0098FD6A /* RemoteMessagingClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteMessagingClient.swift; sourceTree = ""; };
C1B7B52C2894469D0098FD6A /* DefaultVariantManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultVariantManager.swift; sourceTree = ""; };
C1B7B53328944EFA0098FD6A /* CoreDataTestUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataTestUtilities.swift; sourceTree = ""; };
+ C1B7BF522D08640B0024FF56 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1B7BF532D08640B0024FF56 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; };
C1B924B62ACD6E6800EE7B06 /* AutofillNeverSavedTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillNeverSavedTableViewCell.swift; sourceTree = ""; };
C1BF0BA429B63D7200482B73 /* AutofillLoginPromptHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptHelper.swift; sourceTree = ""; };
C1BF0BA729B63E1A00482B73 /* AutofillLoginPromptViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewModelTests.swift; sourceTree = ""; };
C1BF26142C74D10F00F6405E /* SyncPromoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoManager.swift; sourceTree = ""; };
+ C1C1FF412D085A280017ACCE /* CredentialProviderListDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListDetailsView.swift; sourceTree = ""; };
+ C1C1FF422D085A280017ACCE /* CredentialProviderListDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListDetailsViewController.swift; sourceTree = ""; };
+ C1C1FF432D085A280017ACCE /* CredentialProviderListDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListDetailsViewModel.swift; sourceTree = ""; };
+ C1C1FF482D085A4C0017ACCE /* AutofillInterfaceEmailTruncator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillInterfaceEmailTruncator.swift; sourceTree = ""; };
+ C1C1FF492D085A4C0017ACCE /* PasswordHider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordHider.swift; sourceTree = ""; };
+ C1C1FF4C2D085AD70017ACCE /* ActionMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionMessageView.swift; sourceTree = ""; };
+ C1C1FF4D2D085AD70017ACCE /* ActionMessageView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActionMessageView.xib; sourceTree = ""; };
+ C1C1FF4E2D085AD70017ACCE /* FaviconHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconHelper.swift; sourceTree = ""; };
+ C1C1FF4F2D085AD70017ACCE /* NibLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoading.swift; sourceTree = ""; };
+ C1CAAA672CF8B74200C37EE6 /* AutofillCredentialProviderAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProviderAlpha.entitlements; sourceTree = ""; };
+ C1CAAA692CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderActivatedView.swift; sourceTree = ""; };
+ C1CAAA702CF8BC0B00C37EE6 /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtension.swift; sourceTree = ""; };
+ C1CAAA722CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderActivatedViewModel.swift; sourceTree = ""; };
+ C1CAAA772CF8BDF200C37EE6 /* UserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; };
+ C1CAAA792CF8BE0200C37EE6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIResponderExtension.swift; sourceTree = ""; };
+ C1CAAA872CF9FFE100C37EE6 /* CredentialProviderListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListViewController.swift; sourceTree = ""; };
+ C1CAAA892CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListViewModel.swift; sourceTree = ""; };
+ C1CAAA912CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListItemTableViewCell.swift; sourceTree = ""; };
+ C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginItem.swift; sourceTree = ""; };
+ C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListSorting.swift; sourceTree = ""; };
+ C1CAAA9D2CFCB78700C37EE6 /* UIColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtension.swift; sourceTree = ""; };
+ C1CAAA9F2CFCB7C200C37EE6 /* UImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UImageExtension.swift; sourceTree = ""; };
+ C1CAAAA22CFCBBBD00C37EE6 /* UserAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAuthenticator.swift; sourceTree = ""; };
+ C1CAAAA52CFCBD7900C37EE6 /* LockScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenView.swift; sourceTree = ""; };
+ C1CAAAA72CFCBE4800C37EE6 /* EmptySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptySearchView.swift; sourceTree = ""; };
+ C1CAAAA92CFCC13E00C37EE6 /* SecureVaultReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureVaultReporter.swift; sourceTree = ""; };
+ C1CAAAAB2CFCC91D00C37EE6 /* EmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyView.swift; sourceTree = ""; };
+ C1CB0AA52D08629800335287 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1CB0AA62D08629800335287 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; };
C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManager.swift; sourceTree = ""; };
C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManagerTests.swift; sourceTree = ""; };
C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSession.swift; sourceTree = ""; };
C1D21E2E293A599C006E5A05 /* AutofillLoginSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSessionTests.swift; sourceTree = ""; };
+ C1DCF3502D0862330055F8B0 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1DCF3512D0862330055F8B0 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; };
C1E42C7A2C5CD8AD00509204 /* AutofillCredentialsDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillCredentialsDebugViewController.swift; sourceTree = ""; };
+ C1E490582D08646400F86C5A /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1E490592D08646400F86C5A /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = ""; };
+ C1E4E9A52D0861AD00AA39AF /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1E4E9A82D0861AD00AA39AF /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; };
C1EA865F2C74CB6C00E8604D /* SyncPromoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoView.swift; sourceTree = ""; };
C1EA86612C74CB8B00E8604D /* SyncPromoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoViewModel.swift; sourceTree = ""; };
+ C1EF5B212CC0457B002980E6 /* AutofillCredentialProvider.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AutofillCredentialProvider.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ C1EF5B222CC0457B002980E6 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; };
+ C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = ""; };
+ C1EF5B2A2CC0457B002980E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProvider.entitlements; sourceTree = ""; };
C1F341C42A6924000032057B /* EmailAddressPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptView.swift; sourceTree = ""; };
C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewModel.swift; sourceTree = ""; };
C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewController.swift; sourceTree = ""; };
+ C1F883372D08627900DFF79A /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1F883382D08627900DFF79A /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; };
+ C1FE93E52D0863AA009F8F5E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1FE93E62D0863AA009F8F5E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; };
+ C1FEDCEA2D08633100BFBF3F /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; };
+ C1FEDCEB2D08633100BFBF3F /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; };
C1FFBD452C761BE20073622B /* SyncPromoManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoManagerTests.swift; sourceTree = ""; };
C1FFBD472C7749A90073622B /* SyncSettingsViewController+PlatformLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SyncSettingsViewController+PlatformLinks.swift"; sourceTree = "